Programmering

Introduksjon til designmønstre, del 2: Gang-of-four-klassikere revidert

I del 1 av denne tredelte serien som introduserer designmønstre, refererte jeg til Designmønstre: Elementer av gjenbrukbar objektorientert design. Denne klassikeren ble skrevet av Erich Gamma, Richard Helm, Ralph Johnson og John Vlissides, som samlet var kjent som Gang of Four. Som de fleste lesere vil vite, Design mønstre presenterer 23 programvaremønster som passer inn i kategoriene som er diskutert i del 1: Kreasjonell, strukturell og atferdsmessig.

Design mønstre på JavaWorld

David Gearys Java-designmønsterserie er en mesterlig introduksjon til mange av Gang of Four-mønstrene i Java-kode.

Design mønstre er kanonisk lesing for programvareutviklere, men mange nye programmerere blir utfordret av referanseformat og omfang. Hvert av de 23 mønstrene er beskrevet i detalj, i et malformat som består av 13 seksjoner, som kan være mye å fordøye. En annen utfordring for nye Java-utviklere er at Gang of Four-mønstrene kommer fra objektorientert programmering, med eksempler basert på C ++ og Smalltalk, ikke Java-kode.

I denne veiledningen pakker jeg ut to av de vanligste mønstrene - Strategi og besøkende - fra et Java-utviklerperspektiv. Strategi er et ganske enkelt mønster som fungerer som et eksempel på hvordan du får føttene våte med GoF-designmønstrene generelt; Besøkende er mer kompleks og middels i omfang. Jeg begynner med et eksempel som skal avmystifisere den dobbelte utsendelsesmekanismen, som er en viktig del av besøksmønsteret. Deretter demonstrerer jeg besøksmønsteret i en kompilator.

Å følge eksemplene mine her skal hjelpe deg med å utforske og bruke de andre GoF-mønstrene for deg selv. I tillegg vil jeg tilby tips for å få mest mulig ut av Gang of Four-boka og avslutte med et sammendrag av kritikk av bruk av designmønstre i programvareutvikling. Denne diskusjonen kan være spesielt relevant for utviklere som er nye for programmering.

Utpakking Strategi

De Strategi mønster kan du definere en familie av algoritmer som de som brukes til sortering, tekstsammensetning eller layoutadministrasjon. Strategi lar deg også kapsle inn hver algoritme i sin egen klasse og gjøre dem utskiftbare. Hver innkapslet algoritme er kjent som en strategi. Ved kjøretid velger en klient riktig algoritme for kravene.

Hva er en klient?

EN klient er hvilken som helst programvare som samhandler med et designmønster. Selv om det vanligvis er et objekt, kan en klient også være kode i applikasjonens public static void main (String [] args) metode.

I motsetning til dekoratørmønsteret, som fokuserer på å endre et objekt hud, eller utseende, Strategi fokuserer på å endre objektets tarmene, som betyr dens foranderlige oppførsel. Strategi lar deg unngå å bruke flere betingede uttalelser ved å flytte betingede grener til sine egne strategiklasser. Disse klassene stammer ofte fra en abstrakt superklasse, som klienten refererer til og bruker for å samhandle med en bestemt strategi.

Fra et abstrakt perspektiv innebærer strategi Strategi, Betongstrategix, og Kontekst typer.

Strategi

Strategi gir et felles grensesnitt til alle støttede algoritmer. Oppføring 1 presenterer Strategi grensesnitt.

Oppføring 1. void execute (int x) må implementeres av alle konkrete strategier

offentlig grensesnitt Strategi {public void execute (int x); }

Der konkrete strategier ikke er parameterisert med vanlige data, kan du implementere dem via Java-er grensesnitt trekk. Der de er parameterisert, vil du i stedet erklære en abstrakt klasse. For eksempel, høyrejustere, midtjustere og rettferdiggjøre strategier for tekstjustering dele konseptet med a bredde for å utføre tekstjustering. Så du vil erklære dette bredde i den abstrakte klassen.

Betongstrategix

Hver Betongstrategix implementerer det felles grensesnittet og gir en algoritmeimplementering. Listing 2 redskaper Listing 1's Strategi grensesnitt for å beskrive en spesifikk konkret strategi.

Oppføring 2. ConcreteStrategyA utfører en algoritme

offentlig klasse ConcreteStrategyA implementerer strategi {@ Override public void execute (int x) {System.out.println ("kjører strategi A: x =" + x); }}

De ugyldig utføre (int x) metode i Listing 2 identifiserer en spesifikk strategi. Tenk på denne metoden som en abstraksjon for noe mer nyttig, som en bestemt type sorteringsalgoritme (f.eks. Boble Sortering, Innsettingssortering eller Hurtig sortering), eller en bestemt type layoutbehandling (f.eks. Flow Layout, Border Layout, eller Grid Layout).

Oppføring 3 presenterer et sekund Strategi gjennomføring.

Oppføring 3. ConcreteStrategyB utfører en annen algoritme

offentlig klasse ConcreteStrategyB implementerer strategi {@ Override public void execute (int x) {System.out.println ("kjører strategi B: x =" + x); }}

Kontekst

Kontekst gir den sammenhengen den konkrete strategien påberopes i. Liste 2 og 3 viser data som overføres fra en kontekst til en strategi via en metodeparameter. Fordi et generisk strategigrensesnitt deles av alle konkrete strategier, kan det hende at noen av dem ikke krever alle parametere. For å unngå bortkastede parametere (spesielt når du overfører mange forskjellige argumenter til bare noen få konkrete strategier), kan du i stedet sende en referanse til konteksten.

I stedet for å sende en konteksthenvisning til metoden, kan du lagre den i abstraktklassen, slik at metoden din blir kalt parameterløs. Imidlertid vil konteksten måtte spesifisere et mer omfattende grensesnitt som inkluderer kontrakten for tilgang til kontekstdata på en enhetlig måte. Resultatet, som vist i liste 4, er en strammere kobling mellom strategier og deres sammenheng.

Oppføring 4. Kontekst er konfigurert med en ConcreteStrategyx-forekomst

klasse Kontekst {privat Strategistrategi; public Context (Strategistrategi) {setStrategy (strategi); } public void executeStrategy (int x) {strategy.execute (x); } public void setStrategy (Strategistrategi) {this.strategy = strategi; }}

De Kontekst klasse i Listing 4 lagrer en strategi når den opprettes, gir en metode for å endre strategien, og gir en annen metode for å utføre den nåværende strategien. Med unntak av å sende en strategi til konstruktøren, kan dette mønsteret sees i java.awt. Container-klassen, hvis void setLayout (LayoutManager mgr) og ugyldig doLayout () metoder spesifiserer og utfører layout manager strategi.

Strategi Demo

Vi trenger en klient for å demonstrere de forrige typene. Oppføring 5 presenterer a Strategi Demo klientklasse.

Oppføring 5. StrategiDemo

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

En konkret strategi er assosiert med en Kontekst eksempel når konteksten er opprettet. Strategien kan deretter endres via en kontekstmetodeanrop.

Hvis du kompilerer disse klassene og løper Strategi Demo, bør du følge følgende utdata:

utførelsesstrategi A: x = 1 utførelsesstrategi B: x = 2

Gjennomgå besøksmønsteret

Besøkende er det siste mønsteret for programvaredesign som skal vises i Design mønstre. Selv om dette atferdsmønsteret presenteres sist i boken av alfabetiske årsaker, mener noen at det burde komme sist på grunn av dets kompleksitet. Nykommere til besøkende sliter ofte med dette mønsteret for programvaredesign.

Som forklart i Design mønstre, lar en besøkende deg legge til operasjoner i klasser uten å endre dem, litt magi som tilrettelegges av den såkalte doble ekspedisjonsteknikken. For å forstå besøksmønsteret, må vi først fordøye dobbel forsendelse.

Hva er dobbel forsendelse?

Java og mange andre språk støtter polymorfisme (mange former) via en teknikk kjent som dynamisk utsendelse, der en melding blir kartlagt til en bestemt sekvens av kode ved kjøretid. Dynamisk forsendelse er klassifisert som enten enkelt eller flere forsendelser:

  • Enkelt forsendelse: Gitt et klassehierarki der hver klasse implementerer den samme metoden (det vil si at hver underklasse overstyrer den forrige klassens versjon av metoden), og gitt en variabel som er tildelt en forekomst av en av disse klassene, kan typen bare regnes ut ved kjøretid. Anta for eksempel at hver klasse implementerer metode skrive ut(). Anta at også en av disse klassene blir instantiert ved kjøretid og variabelen tilordnet variabelen en. Når Java-kompilatoren møter a.print ();, det kan bare bekrefte det entypen inneholder en skrive ut() metode. Den vet ikke hvilken metode man skal ringe. Ved kjøretid undersøker den virtuelle maskinen referansen i variabelen en og finner ut den faktiske typen for å kalle riktig metode. Denne situasjonen, der en implementering er basert på en enkelt type (typen forekomst), er kjent som enkelt forsendelse.
  • Flere forsendelser: I motsetning til i en enkelt forsendelse, der et enkelt argument bestemmer hvilken metode for navnet du skal påkalle, flere forsendelser bruker alle argumentene. Med andre ord generaliserer den dynamisk forsendelse til å jobbe med to eller flere objekter. (Merk at argumentet i en enkelt forsendelse vanligvis er spesifisert med en periodeseparator til venstre for metodenavnet som blir kalt, for eksempel en i a.print ().)

Endelig, dobbel forsendelse er et spesielt tilfelle av flere forsendelser der kjøretidstyper av to objekter er involvert i samtalen. Selv om Java støtter enkel forsendelse, støtter den ikke dobbelt forsendelse direkte. Men vi kan simulere det.

Stoler vi for mye på dobbel forsendelse?

Blogger Derek Greer mener at bruk av dobbel forsendelse kan indikere et designproblem, som kan påvirke applikasjonens vedlikeholdsevne. Les Greers bloggpost "Double dispatch is a code smell" og tilhørende kommentarer for detaljer.

Simulering av dobbel forsendelse i Java-kode

Wikipedia's oppføring om dobbel forsendelse gir et C ++ - basert eksempel som viser at det er mer enn funksjonsoverbelastning. I Listing 6 presenterer jeg Java-ekvivalenten.

Oppføring 6. Dobbel forsendelse i Java-kode

public class DDDemo {public static void main (String [] args) {Asteroid theAsteroid = new Asteroid (); SpaceShip theSpaceShip = nytt SpaceShip (); ApolloSpacecraft theApolloSpacecraft = nytt ApolloSpacecraft (); theAsteroid.collideWith (theSpaceShip); theAsteroid.collideWith (theApolloSpacecraft); System.out.println (); ExplodingAsteroid theExplodingAsteroid = ny ExplodingAsteroid (); theExplodingAsteroid.collideWith (theSpaceShip); theExplodingAsteroid.collideWith (theApolloSpacecraft); System.out.println (); Asteroid theAsteroidReference = theExplodingAsteroid; theAsteroidReference.collideWith (theSpaceShip); theAsteroidReference.collideWith (theApolloSpacecraft); System.out.println (); SpaceShip theSpaceShipReference = thePolloSpacecraft; theAsteroid.collideWith (theSpaceShipReference); theAsteroidReference.collideWith (theSpaceShipReference); System.out.println (); theSpaceShipReference = thePolloSpacecraft; theAsteroidReference = theExplodingAsteroid; theSpaceShipReference.collideWith (the Asteroid); theSpaceShipReference.collideWith (theAsteroidReference); }} class SpaceShip {void collideWith (Asteroid inAsteroid) {inAsteroid.collideWith (this); }} klasse ApolloSpacecraft utvider SpaceShip {void collideWith (Asteroid inAsteroid) {inAsteroid.collideWith (this); }} klasse Asteroid {void collideWith (SpaceShip s) {System.out.println ("Asteroid hit a SpaceShip"); } void collideWith (ApolloSpacecraft as) {System.out.println ("Asteroid hit a ApolloSpacecraft"); }} klasse ExplodingAsteroid utvider Asteroid {void collideWith (SpaceShip s) {System.out.println ("ExplodingAsteroid hit a SpaceShip"); } void collideWith (ApolloSpacecraft as) {System.out.println ("ExplodingAsteroid hit a ApolloSpacecraft"); }}

Oppføring 6 følger C ++ -motparten så tett som mulig. De siste fire linjene i hoved() metode sammen med void collideWith (Asteroid in Asteroid) metoder i Romskip og ApolloSpacecraft demonstrere og simulere dobbel forsendelse.

Tenk på følgende utdrag fra slutten av hoved():

theSpaceShipReference = thePolloSpacecraft; theAsteroidReference = theExplodingAsteroid; theSpaceShipReference.collideWith (the Asteroid); theSpaceShipReference.collideWith (theAsteroidReference);

Den tredje og fjerde linjen bruker en enkelt forsendelse for å finne ut riktig Kollidere med() metode (i Romskip eller ApolloSpacecraft) å påberope seg. Denne beslutningen tas av den virtuelle maskinen basert på typen referanse som er lagret i theSpaceShipReference.

Innenfra Kollidere med(), inAsteroid.collideWith (dette); bruker enkelt forsendelse for å finne ut riktig klasse (Asteroid eller ExplodingAsteroid) som inneholder ønsket Kollidere med() metode. Fordi Asteroid og ExplodingAsteroid overbelastning Kollidere med(), typen argument dette (Romskip eller ApolloSpacecraft) brukes til å skille mellom det riktige Kollidere med() metode for å ringe.

Og med det har vi oppnådd dobbelt forsendelse. For å oppsummere ringte vi først Kollidere med() i Romskip eller ApolloSpacecraft, og brukte deretter argumentet og dette å ringe en av Kollidere med() metoder i Asteroid eller ExplodingAsteroid.

Når du løper DDDemo, bør du følge følgende utdata:

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