Programiranje

Uvod v oblikovalske vzorce, 2. del: Ponovno obiskane klasike štirih tolp

V prvem delu te tridelne serije, ki uvaja vzorce oblikovanja, sem omenil Vzorci oblikovanja: elementi predmetno usmerjenega oblikovanja za večkratno uporabo. To klasiko so napisali Erich Gamma, Richard Helm, Ralph Johnson in John Vlissides, ki so bili skupaj znani kot Gang of Four. Kot bo vedela večina bralcev, Vzorci oblikovanja predstavlja 23 vzorcev oblikovanja programske opreme, ki ustrezajo kategorijam, obravnavanim v 1. delu: kreativni, strukturni in vedenjski.

Oblikujte vzorce na JavaWorldu

Serija vzorčnih vzorcev Java David Geary je mojstrski uvod v številne vzorce Gang of Four v kodi Java.

Vzorci oblikovanja je kanonično branje za razvijalce programske opreme, vendar je veliko novih programerjev izzvano zaradi njegove referenčne oblike in obsega. Vsak od 23 vzorcev je podrobno opisan v obliki predloge, sestavljene iz 13 odsekov, ki jih je mogoče veliko prebaviti. Drug izziv za nove razvijalce Jave je, da vzorci Gang of Four izvirajo iz objektno usmerjenega programiranja s primeri, ki temeljijo na C ++ in Smalltalk, ne na Java kodi.

V tej vadnici bom z vidika razvijalca Jave razpakiral dva najpogosteje uporabljena vzorca - Strategija in Obiskovalec. Strategija je dokaj preprost vzorec, ki služi kot primer, kako si navlažite noge z vzorci oblikovanja GoF na splošno; Obiskovalci so bolj zapleteni in vmesni. Začel bom s primerom, ki bi moral demistificirati mehanizem dvojne odpreme, ki je pomemben del vzorca Visitor. Potem bom prikazal vzorec Visitor v primeru uporabe prevajalnika.

Upoštevanje mojih primerov tukaj bi vam moralo pomagati pri raziskovanju in uporabi drugih vzorcev GoF. Poleg tega bom ponudil nasvete, kako kar najbolje izkoristiti knjigo Gang of Four, in zaključil s povzetkom kritik uporabe vzorcev oblikovanja pri razvoju programske opreme. Ta razprava bi lahko bila še posebej pomembna za razvijalce, ki so novi v programiranju.

Strategija razpakiranja

The Strategija vzorec vam omogoča določitev družine algoritmov, kot so tisti, ki se uporabljajo za razvrščanje, sestavljanje besedila ali upravljanje postavitve. Strategija vam omogoča tudi, da vsak algoritem vključite v svoj razred in ga naredite zamenljiv. Vsak enkapsuliran algoritem je znan kot strategijo. Med izvajanjem stranka izbere ustrezen algoritem za svoje zahteve.

Kaj je stranka?

A stranka je kateri koli del programske opreme, ki deluje z vzorcem oblikovanja. Čeprav je odjemalec običajno objekt, je lahko tudi koda znotraj aplikacije javna statična void main (String [] args) metoda.

Za razliko od vzorca Decorator, ki se osredotoča na spreminjanje predmeta kožoali videz se osredotoča na spreminjanje predmeta črevesje, kar pomeni njegovo spremenljivo vedenje. Strategija vam omogoča, da se izognete uporabi več pogojnih stavkov s premikanjem pogojnih vej v lastne razrede strategij. Ti razredi pogosto izhajajo iz abstraktnega superrazreda, ki ga stranka sklicuje in uporablja za interakcijo s posebno strategijo.

Z abstraktnega vidika Strategija vključuje Strategija, ConcreteStrategyx, in Kontekst vrste.

Strategija

Strategija ponuja skupen vmesnik za vse podprte algoritme. Seznam 1 predstavlja Strategija vmesnik.

Seznam 1. void izvedbo (int x) morajo izvajati vse konkretne strategije

strategija javnega vmesnika {javno void izvedba (int x); }

Kjer konkretne strategije niso parametrizirane s skupnimi podatki, jih lahko implementirate prek Jave vmesnik funkcija. Kjer so parametrizirani, bi namesto tega razglasili abstraktni razred. Na primer, strategije desne poravnave, sredinske poravnave in utemeljitve poravnave besedila delijo koncept a premer v katerem se izvede poravnava besedila. Torej bi to izjavili premer v abstraktnem razredu.

ConcreteStrategyx

Vsak ConcreteStrategyx izvaja skupni vmesnik in zagotavlja izvedbo algoritma. Seznam 2 izvaja seznam 1 Strategija vmesnik za opis konkretne konkretne strategije.

Seznam 2. ConcreteStrategyA izvaja en algoritem

javni razred ConcreteStrategyA izvaja strategijo {@Override public void execute (int x) {System.out.println ("izvrševanje strategije A: x =" + x); }}

The void izvedba (int x) metoda v seznamu 2 opredeljuje določeno strategijo. Razmislite o tej metodi kot o abstrakciji za kaj bolj uporabnega, na primer za določeno vrsto algoritma za razvrščanje (npr. Bubble Sort, Insertion Sort ali Quick Sort) ali določeno vrsto upravitelja postavitve (npr. Layout Flow, Border Layout ali Postavitev mreže).

Seznam 3 predstavlja drugo Strategija izvajanje.

Seznam 3. ConcreteStrategyB izvaja drug algoritem

javni razred ConcreteStrategyB izvaja strategijo {@Override public void execute (int x) {System.out.println ("izvedba strategije B: x =" + x); }}

Kontekst

Kontekst podaja kontekst, v katerem se sklicuje na konkretno strategijo. Seznam 2 in 3 prikazujeta podatke, ki se posredujejo iz konteksta v strategijo prek parametra metode. Ker si vmesnik generične strategije delijo vse konkretne strategije, nekatere od njih morda ne bodo zahtevale vseh parametrov. Da bi se izognili zapravljanju parametrov (zlasti pri posredovanju različnih vrst argumentov le nekaj konkretnim strategijam), lahko namesto tega posredujete sklic na kontekst.

Namesto da posredujete referenčno referenco na metodo, jo lahko shranite v abstraktni razred, tako da vaša metoda kliče brez parametrov. Vendar bi moral kontekst določiti obsežnejši vmesnik, ki bi vključeval pogodbo o enotnem dostopu do podatkov o kontekstu. Rezultat, kot je prikazano v seznamu 4, je tesnejše povezovanje strategij in njihovega konteksta.

Seznam 4. Kontekst je konfiguriran s primerkom ConcreteStrategyx

razred Kontekst {zasebna strategija strategije; javni kontekst (strategija strategije) {setStrategy (strategija); } javna praznina executeStrategy (int x) {strategy.execute (x); } public void setStrategy (strategija strategije) {this.strategy = strategija; }}

The Kontekst class v seznamu 4 shrani strategijo, ko je ta ustvarjena, ponuja metodo za poznejšo spremembo strategije in drugo metodo za izvajanje trenutne strategije. Ta vzorec je razen posredovanja strategije konstruktorju viden v razredu java.awt .Container, katerega void setLayout (LayoutManager mgr) in void doLayout () metode podajajo in izvajajo strategijo upravitelja postavitve.

StrategyDemo

Za predstavitev prejšnjih vrst potrebujemo stranko. Seznam 5 predstavlja a StrategyDemo odjemalski razred.

Seznam 5. StrategyDemo

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

Konkretna strategija je povezana z Kontekst primer, ko je kontekst ustvarjen. Strategijo lahko pozneje spremenite s klicem metode konteksta.

Če prevedete te razrede in zaženete StrategyDemo, upoštevajte naslednje rezultate:

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

Ponovno pregledovanje vzorca obiskovalca

Obiskovalec je končni vzorec oblikovanja programske opreme, ki bo prikazan v Vzorci oblikovanja. Čeprav je ta vedenjski vzorec v knjigi iz abecednih razlogov predstavljen zadnji, nekateri menijo, da bi moral zaradi svoje zapletenosti biti zadnji. Novinci v Visitorju se pogosto spopadajo s tem vzorcem oblikovanja programske opreme.

Kot je razloženo v Vzorci oblikovanja, obiskovalec vam omogoča dodajanje operacij v razrede, ne da bi jih spreminjal, nekaj čarovnije, ki jo olajša tako imenovana tehnika dvojne odpreme. Da bi razumeli vzorec obiskovalca, moramo najprej prebaviti dvojno odpremo.

Kaj je dvojna odprema?

Podpora za Java in številne druge jezike polimorfizem (veliko oblik) s tehniko, znano kot dinamična odprema, v katerem se sporočilo preslika na določeno zaporedje kode med izvajanjem. Dinamična odprema je razvrščena kot enojna ali večkratna odprema:

  • Posamezna odprema: Glede na hierarhijo razredov, kjer vsak razred izvaja isto metodo (to pomeni, da vsak podrazred preglasi različico metode prejšnjega razreda), in glede na spremenljivko, ki ji je dodeljen primerek enega od teh razredov, je tip mogoče ugotoviti samo na čas izvajanja. Denimo, da vsak razred izvaja metodo natisni (). Recimo tudi, da je eden od teh razredov instantiran med izvajanjem in je njegova spremenljivka dodeljena spremenljivki a. Ko naleti prevajalnik Java a.print ();, lahko samo to preveri avrsta vsebuje a natisni () metoda. Ne ve, katero metodo poklicati. Med izvajanjem navidezni stroj pregleda referenco v spremenljivki a in ugotovi dejansko vrsto, da pokliče pravo metodo. Ta položaj, v katerem izvedba temelji na enem tipu (tipu primerka), je znan kot enojna odprema.
  • Večkratna odprema: Za razliko od ene odpreme, kjer en argument določa, katero metodo tega imena uporabiti, večkratna odprema uporablja vse svoje argumente. Z drugimi besedami, posplošuje dinamično pošiljanje za delo z dvema ali več predmeti. (Upoštevajte, da je argument v enem odpošiljanju običajno podan z ločevalnikom pik levo od imena metode, na primer a v a.print ().)

Končno, dvojna odprema je poseben primer večkratne odpreme, pri kateri sta v klic vključena izvajalna tipa dveh predmetov. Čeprav Java podpira eno odpošiljanje, dvojne odpreme ne podpira neposredno. Lahko pa ga simuliramo.

Se preveč zanašamo na dvojno odpremo?

Bloger Derek Greer verjame, da lahko uporaba dvojne odpreme kaže na težavo z oblikovanjem, ki bi lahko vplivala na vzdrževalnost aplikacije. Za podrobnosti preberite objavo v blogu Greer's "Double dispatch is a code von" in povezane komentarje.

Simulacija dvojne odpreme v kodi Java

Prispevek Wikipedije o dvojni odpremi ponuja primer na osnovi C ++, ki kaže, da gre za več kot le preobremenitev funkcije. V seznamu 6 predstavljam ekvivalent Java.

Seznam 6. Dvojna odprema v kodi Java

javni razred DDDemo {public static void main (String [] args) {Asteroid theAsteroid = nov Asteroid (); SpaceShip theSpaceShip = nov SpaceShip (); ApolloSpacecraft theApolloSpacecraft = novo ApolloSpacecraft (); theAsteroid.collideWith (theSpaceShip); theAsteroid.collideWith (theApolloSpacecraft); System.out.println (); ExplodingAsteroid theExplodingAsteroid = novo ExplodingAsteroid (); theExplodingAsteroid.collideWith (theSpaceShip); theExplodingAsteroid.collideWith (vesoljsko ploviloApollo); System.out.println (); Asteroid theAsteroidReference = theExplodingAsteroid; theAsteroidReference.collideWith (theSpaceShip); theAsteroidReference.collideWith (theApolloSpacecraft); System.out.println (); SpaceShip theSpaceShipReference = theApolloSpacecraft; theAsteroid.collideWith (theSpaceShipReference); theAsteroidReference.collideWith (theSpaceShipReference); System.out.println (); theSpaceShipReference = theApolloSpacecraft; theAsteroidReference = theExplodingAsteroid; theSpaceShipReference.collideWith (theAsteroid); theSpaceShipReference.collideWith (theAsteroidReference); }} razred SpaceShip {void collideWith (Asteroid inAsteroid) {inAsteroid.collideWith (this); }} razred ApolloSpacecraft razširja SpaceShip {void collideWith (Asteroid inAsteroid) {inAsteroid.collideWith (this); }} razred Asteroid {void collideWith (SpaceShip s) {System.out.println ("Asteroid zadel vesoljsko ladjo"); } void collideWith (ApolloSpacecraft as) {System.out.println ("Asteroid zadel ApolloSpacecraft"); }} razred ExplodingAsteroid razširja Asteroid {void collideWith (SpaceShip s) {System.out.println ("ExplodingAsteroid hit a SpaceShip"); } void collideWith (ApolloSpacecraft as) {System.out.println ("ExplodingAsteroid zadel ApolloSpacecraft"); }}

Seznam 6 čim bolj natančno sledi svojemu primerku C ++. Zadnje štiri vrstice v glavni () metoda skupaj z trk praznineWith (Asteroid inAsteroid) metode v SpaceShip in ApolloSpacecraft demonstrirati in simulirati dvojno odpremo.

Razmislite o naslednjem odlomku s konca leta glavni ():

theSpaceShipReference = theApolloSpacecraft; theAsteroidReference = theExplodingAsteroid; theSpaceShipReference.collideWith (theAsteroid); theSpaceShipReference.collideWith (theAsteroidReference);

V tretji in četrti vrstici se z eno pošiljanjem ugotovi pravilnost collideWith () metoda (v SpaceShip ali ApolloSpacecraft) za sklic. To odločitev sprejme navidezni stroj na podlagi vrste sklica, shranjenega v theSpaceShipReference.

Od znotraj collideWith (), inAsteroid.collideWith (to); uporablja eno pošiljanje, da ugotovi pravi razred (Asteroid ali ExplodingAsteroid), ki vsebuje želeno collideWith () metoda. Ker Asteroid in ExplodingAsteroid preobremenitev collideWith (), vrsta argumenta to (SpaceShip ali ApolloSpacecraft) se uporablja za razlikovanje med pravilnimi collideWith () način klica.

In s tem smo dosegli dvojno odpremo. Za povzetek smo najprej poklicali collideWith () v SpaceShip ali ApolloSpacecraftin nato uporabil svoj argument in to poklicati enega od collideWith () metode v Asteroid ali ExplodingAsteroid.

Ko tečeš DDDemo, upoštevajte naslednje rezultate:

$config[zx-auto] not found$config[zx-overlay] not found