Programavimas

Dizaino modelių įvadas. 2 dalis. Peržiūrėta keturių klasių gauja

Šios trijų dalių serijos, pristatančios dizaino modelius, 1 dalyje minėjau Dizaino modeliai: daugkartinio naudojimo objektinio dizaino elementai. Šią klasiką parašė Erichas Gamma, Richardas Helmas, Ralfas Johnsonas ir Johnas Vlissidesas, kurie bendrai buvo žinomi kaip „Keturių gauja“. Kaip žinos dauguma skaitytojų, Dizaino modeliai pateikia 23 programinės įrangos projektavimo modelius, kurie tinka kategorijoms, aptartoms 1 dalyje: Kūrybinis, struktūrinis ir elgesio.

Dizaino modeliai „JavaWorld“

Davido Geary „Java“ dizaino modelių serija yra meistriškas įvadas į daugelį „Java“ kodo keturių modelių gaujos.

Dizaino modeliai yra kanoninis skaitymas programinės įrangos kūrėjams, tačiau daugeliui naujų programuotojų kyla iššūkių dėl jo nuorodos formato ir apimties. Kiekvienas iš 23 modelių yra išsamiai aprašytas šablono formatu, susidedančiu iš 13 skyrių, o tai gali būti daug virškinama. Dar vienas iššūkis naujiems „Java“ kūrėjams yra tas, kad „Gang of Four“ modeliai kyla iš objektinio programavimo, pateikiant pavyzdžius, paremtus „C ++“ ir „Smalltalk“, o ne „Java“ kodu.

Šioje pamokoje iš „Java“ kūrėjo perspektyvos išpakuosiu du dažniausiai naudojamus modelius - strategiją ir lankytoją. Strategija yra gana paprastas modelis, kuris yra pavyzdys, kaip paprastai sušlapinti kojas naudojant „GoF“ dizaino modelius; Lankytojas yra sudėtingesnis ir vidutinės apimties. Pradėsiu nuo pavyzdžio, kuris turėtų demistifikuoti dvigubą išsiuntimo mechanizmą, kuris yra svarbi lankytojo modelio dalis. Tada parodysiu „Visitor“ modelį kompiliatoriaus naudojimo atveju.

Čia pateikiami mano pavyzdžiai turėtų padėti jums ištirti ir naudoti kitus „GoF“ modelius. Be to, aš pasiūlysiu patarimų, kaip kuo geriau išnaudoti „Keturių gauja“, ir baigsiu kritikos, susijusios su dizaino modelių naudojimu kuriant programinę įrangą, santrauka. Ta diskusija gali būti ypač aktuali programuotojams, naujiems programuotojams.

Išpakavimo strategija

Strategija Šablonas leidžia apibrėžti algoritmų šeimą, pvz., tuos, kurie naudojami rūšiavimui, teksto kompozicijai ar išdėstymo valdymui. Strategija taip pat leidžia kiekvieną algoritmą apjungti į savo klasę ir padaryti juos keičiamus. Kiekvienas kapsuliuotas algoritmas yra žinomas kaip a strategija. Vykdymo metu klientas parenka savo poreikiams tinkamą algoritmą.

Kas yra klientas?

A klientas yra bet kuri programinė įranga, sąveikaujanti su dizaino modeliu. Nors klientas paprastai yra objektas, jis taip pat gali būti programos kodas public static void main (String [] argumentai) metodas.

Skirtingai nuo „Decorator“ modelio, kuris orientuotas į objekto keitimą oda, arba išvaizda, strategija orientuota į objekto pakeitimą viduriai, reiškiančia jo kintantį elgesį. Strategija leidžia jums išvengti kelių sąlyginių sakinių perkėlimo sąlyginių šakų į savo strategijos klases. Šios klasės dažnai kyla iš abstrakčios superklasės, kurią klientas nurodo ir naudoja sąveikaudamas su konkrečia strategija.

Žvelgiant iš abstrakčios perspektyvos, strategija apima Strategija, Betono strategijaxir Kontekstas tipai.

Strategija

Strategija suteikia bendrą sąsają su visais palaikomais algoritmais. 1 sąraše pateikiama Strategija sąsaja.

Sąrašas 1. void execute (int x) turi būti įgyvendintas pagal visas konkrečias strategijas

viešosios sąsajos strategija {public void execute (int x); }

Jei konkrečios strategijos nėra parametruojamos pagal bendrus duomenis, jas galite įgyvendinti per „Java“ sąsaja funkcija. Kur jie yra parametrizuoti, jūs vietoje to deklaruotumėte abstrakčią klasę. Pavyzdžiui, lygiuoti dešinėje, sulygiuoti centre ir pagrįsti teksto sulyginimo strategijas sutampa su a samprata plotis kurioje reikia atlikti teksto lygiavimą. Taigi jūs tai deklaruotumėte plotis abstrakčioje klasėje.

Betono strategijax

Kiekvienas Betono strategijax įgyvendina bendrą sąsają ir pateikia algoritmo įgyvendinimą. 2 sąrašas įgyvendina 1 sąrašą Strategija sąsaja konkrečiai konkrečiai strategijai apibūdinti.

Sąrašas 2. „ConcreteStrategyA“ vykdo vieną algoritmą

viešoji klasė „ConcreteStrategyA“ įgyvendina strategiją {@Override public void execute (int x) {System.out.println ("vykdymo strategija A: x =" + x); }}

negaliojantis vykdymas (int x) metodas 2 sąraše nurodo konkrečią strategiją. Pagalvokite apie šį metodą kaip apie kažko naudingesnio abstrakciją, pvz., Konkretaus rūšiavimo algoritmo rūšį (pvz., „Bubble Sort“, „Insertion Sort“ arba „Quick Sort“) arba konkretaus tipo išdėstymo tvarkyklę (pvz., „Flow Layout“, „Border Layout“ ar Tinklelio išdėstymas).

3 sąrašas pateikia antrą Strategija įgyvendinimas.

3. sąrašas „ConcreteStrategyB“ vykdo kitą algoritmą

viešoji klasė „ConcreteStrategyB“ įgyvendina strategiją {@Override public void execute (int x) {System.out.println ("vykdymo strategija B: x =" + x); }}

Kontekstas

Kontekstas pateikia kontekstą, kuriame remiamasi konkrečia strategija. 2 ir 3 sąrašuose rodomi duomenys, perduodami iš konteksto strategijai per metodo parametrą. Kadangi bendrą strategijos sąsają naudoja visos konkrečios strategijos, kai kurioms iš jų gali prireikti ne visų parametrų. Kad išvengtumėte iššvaistytų parametrų (ypač kai pateikiate daug įvairių argumentų tik kelioms konkrečioms strategijoms), galite pateikti nuorodą į kontekstą.

Užuot perdavę konteksto nuorodą į metodą, galite ją išsaugoti abstrakčioje klasėje, todėl metodo skambučiai yra be parametrų. Tačiau kontekste reikėtų nurodyti platesnę sąsają, į kurią būtų įtraukta sutartis dėl vienodo prieigos prie konteksto duomenų. Rezultatas, kaip parodyta 4 sąraše, yra glaudesnis strategijų ir jų konteksto susiejimas.

Sąrašas 4. Kontekstas sukonfigūruotas naudojant „ConcreteStrategyx“ egzempliorių

klasės kontekstas {privati ​​strategijos strategija; viešasis kontekstas (strategijos strategija) {setStrategy (strategija); } public void executeStrategy (int x) {strategija.execute (x); } public void setStrategy (strategijos strategija) {this.strategy = strategija; }}

Kontekstas 4 sąraše esanti klasė saugo strategiją, kai ji sukuriama, pateikia metodą, kaip vėliau pakeisti strategiją, ir pateikia kitą metodą dabartinei strategijai vykdyti. Išskyrus strategijos perdavimą konstruktoriui, šį modelį galima pamatyti java.awt .Container klasėje, kurios void setLayout („LayoutManager“ valdytojas) ir negaliojantis „doLayout“ () metodai nurodo ir vykdo maketo tvarkytuvo strategiją.

„StrategyDemo“

Mums reikia kliento, kuris pademonstruotų ankstesnius tipus. 5 sąraše pateikiami a „StrategyDemo“ klientų klasė.

5. „StrategyDemo“ sąrašas

public class StrategyDemo {public static void main (String [] args) {Context context = new Context (new ConcreteStrategyA ()); context.executeStrategy (1); context.setStrategy (nauja ConcreteStrategyB ()); context.executeStrategy (2); }}

Konkreti strategija siejama su a Kontekstas pavyzdžiui, kai sukuriamas kontekstas. Vėliau strategiją galima pakeisti naudojant konteksto metodo iškvietimą.

Jei sudarysite šias klases ir paleisite „StrategyDemo“, turėtumėte stebėti šį rezultatą:

vykdymo strategija A: x = 1 vykdymo strategija B: x = 2

Lankytojo modelio peržiūra

Lankytojas yra galutinis programinės įrangos projektavimo modelis, kuris turi būti rodomas Dizaino modeliai. Nors šis elgesio modelis knygoje pateikiamas paskutinis dėl abėcėlės priežasčių, kai kurie mano, kad jis turėtų būti paskutinis dėl savo sudėtingumo. Nauji lankytojo lankytojai dažnai kovoja su šiuo programinės įrangos dizaino modeliu.

Kaip paaiškinta Dizaino modeliai, lankytojas leidžia pridėti operacijas į klases jų nekeičiant, šiek tiek magijos, kurią palengvina vadinamoji dvigubo išsiuntimo technika. Norėdami suprasti lankytojų modelį, pirmiausia turime suvirškinti dvigubą išsiuntimą.

Kas yra dvigubas išsiuntimas?

„Java“ ir daugelis kitų kalbų palaiko polimorfizmas (daugybė formų) naudojant techniką, žinomą kaip dinamiškas išsiuntimas, kuriame pranešimas vykdymo metu priskiriamas konkrečiai kodo sekai. Dinaminis siuntimas klasifikuojamas kaip vienas arba daugkartinis:

  • Vienkartinis išsiuntimas: Atsižvelgiant į klasės hierarchiją, kai kiekviena klasė taiko tą patį metodą (tai yra, kiekvienas poklasis pakeičia ankstesnės klasės metodo versiją), ir atsižvelgiant į kintamąjį, kuriam priskirtas vienos iš šių klasių egzempliorius, tipą galima išsiaiškinti tik vykdymo laikas. Pavyzdžiui, tarkime, kad kiekviena klasė įgyvendina metodą spausdinti (). Tarkime, kad viena iš šių klasių yra egzemplioriumi vykdymo metu ir jos kintamasis priskiriamas kintamajam a. Kai „Java“ kompiliatorius susiduria a. atspaudas ();, ji gali tik tai patikrinti atipo yra a spausdinti () metodas. Ji nežino, kuriuo metodu skambinti. Vykdymo metu virtualioji mašina tikrina nuorodą kintamajame a ir išsiaiškina tikrąjį tipą, kad būtų galima iškviesti teisingą metodą. Ši situacija, kai diegimas pagrįstas vienu tipu (egzemplioriaus tipu), yra žinomas kaip vienas siuntimas.
  • Daugkartinis išsiuntimas: Skirtingai nuo vieno siuntimo, kai vienas argumentas nustato, kurį šio pavadinimo metodą naudoti, daugkartinis išsiuntimas naudojasi visais savo argumentais. Kitaip tariant, tai apibendrina dinamišką siuntimą dirbti su dviem ar daugiau objektų. (Atkreipkite dėmesį, kad vieno siuntimo argumentas paprastai nurodomas taškų skirikliu kairėje nuo iškviečiamo metodo pavadinimo, pvz., a į a.print ().)

Pagaliau, dvigubas išsiuntimas yra specialus daugybinio siuntimo atvejis, kai į skambutį įtraukiami dviejų objektų vykdymo laiko tipai. Nors „Java“ palaiko vieną siuntimą, ji nepalaiko dvigubo siuntimo tiesiogiai. Bet mes galime tai imituoti.

Ar mes pernelyg pasikliaujame dvigubu išsiuntimu?

Tinklaraštininkas Derekas Greeris mano, kad dvigubo siuntimo naudojimas gali reikšti dizaino problemą, kuri gali turėti įtakos programos išlaikomumui. Perskaitykite Greerio dienoraščio įrašą „Dvigubas siuntimas yra kodo kvapas“ ir susijusius komentarus.

Imituojamas dvigubas siuntimas Java kodu

Vikipedijos įrašas apie dvigubą siuntimą pateikia C ++ pagrįstą pavyzdį, kuris rodo, kad tai daugiau nei funkcijų perkrova. 6 sąraše pateikiu „Java“ atitikmenį.

Sąrašas 6. Dvigubas „Java“ kodo išsiuntimas

public class DDDemo {public static void main (String [] args) {Asteroidas theAsteroid = naujas asteroidas (); SpaceShip theSpaceShip = naujas kosminis laivas (); ApolloSpacecraft theApolloSpacecraft = naujas ApolloSpacecraft (); theAsteroid.collideWith (theSpaceShip); theAsteroid.collideWith („ApolloSpacecraft“); System.out.println (); ExplodingAsteroid theExplodingAsteroid = naujas ExplodingAsteroid (); theExplodingAsteroid.collideWith (theSpaceShip); theExplodingAsteroid.collideWith („ApolloSpacecraft“); System.out.println (); Asteroidas theAsteroidReference = sprogstantis asteroidas; theAsteroidReference.collideWith (theSpaceShip); theAsteroidReference.collideWith („ApolloSpacecraft“); System.out.println (); SpaceShip theSpaceShipReference = theApolloSpacecraft; theAsteroid.collideWith (theSpaceShipReference); theAsteroidReference.collideWith (theSpaceShipReference); System.out.println (); theSpaceShipReference = theApolloSpacecraft; theAsteroidReference = theExplodingAsteroid; theSpaceShipReference.collideWith (asteroidas); theSpaceShipReference.collideWith (theAsteroidReference); }} klasės kosminis laivas {void collideWith (Asteroidas inAsteroid) {inAsteroid.collideWith (this); }} klasės „ApolloSpacecraft“ išplečia erdvėlaivį {void collideWith (Asteroidas inAsteroid) {inAsteroid.collideWith (this); }} klasės asteroidas {void collideWith (SpaceShip s) {System.out.println ("Asteroidas pataikė į kosminį laivą"); } void collideWith (ApolloSpacecraft as) {System.out.println ("Asteroidas pataikė į ApolloSpacecraft"); }} klasė „ExplodingAsteroid“ pratęsia asteroidą {void collideWith (SpaceShip s) {System.out.println ("ExplodingAsteroid hit the SpaceShip"); } void collideWith (ApolloSpacecraft as) {System.out.println ("Sprogstantis asteroidas pataikė į ApolloSpacecraft"); }}

6 sąrašas kuo tiksliau seka C ++ atitikmenį. Paskutinės keturios eilutės pagrindinis () metodas kartu su tuštuma susiduria su (asteroidas į asteroidą) metodai Kosminis laivas ir „Apollo“ erdvėlaivis pademonstruoti ir imituoti dvigubą išsiuntimą.

Apsvarstykite šią ištrauką iš pabaigos pagrindinis ():

theSpaceShipReference = theApolloSpacecraft; theAsteroidReference = sprogstantis asteroidas; theSpaceShipReference.collideWith (asteroidas); theSpaceShipReference.collideWith (theAsteroidReference);

Trečioje ir ketvirtoje eilutėse naudojamas vienas siuntimas, kad išsiaiškintumėte teisingą susidurti su() metodas (in Kosminis laivas arba „Apollo“ erdvėlaivis) kreiptis. Šį sprendimą priima virtuali mašina, remdamasi nuorodos, saugomos aplanke, tipu theSpaceShipReference.

Iš vidaus susidurti su(), inAsteroid.collideWith (tai); naudoja vieną siuntimą, kad išsiaiškintų teisingą klasę (Asteroidas arba Sprogstantis asteroidas), kuriame yra norimas susidurti su() metodas. Nes Asteroidas ir Sprogstantis asteroidas perkrova susidurti su(), argumento tipas tai (Kosminis laivas arba „Apollo“ erdvėlaivis) naudojamas atskirti teisingą susidurti su() metodas skambinti.

Ir tuo mes įvykdėme dvigubą siuntimą. Norėdami pakartoti, mes pirmiausia paskambinome susidurti su() į Kosminis laivas arba „Apollo“ erdvėlaivis, tada panaudojo savo argumentą ir tai paskambinti vienam iš susidurti su() metodai Asteroidas arba Sprogstantis asteroidas.

Kai bėgsi DDDemo, turėtumėte stebėti šį rezultatą: