Statligt designmønster i Java

1. Oversigt

I denne vejledning introducerer vi et af de adfærdsmæssige GoF-designmønstre - tilstandsmønsteret.

Først giver vi et overblik over dets formål og forklarer det problem, det forsøger at løse. Derefter ser vi på statens UML-diagram og implementering af det praktiske eksempel.

2. Statligt designmønster

Hovedideen med statsmønster er at tillad objektet at ændre dets adfærd uden at ændre sin klasse. Ved at implementere den skal koden også forblive renere uden mange hvis / ellers udsagn.

Forestil dig, at vi har en pakke, der sendes til et posthus, selve pakken kan bestilles, derefter leveres til et posthus og endelig modtages af en klient. Afhængigt af den aktuelle tilstand vil vi nu udskrive dens leveringsstatus.

Den enkleste tilgang ville være at tilføje nogle boolske flag og anvende enkle hvis / ellers udsagn inden for hver af vores metoder i klassen. Det vil ikke komplicere det meget i et simpelt scenario. Det kan dog komplicere og forurene vores kode, når vi får flere stater til at behandle, hvilket vil resultere i endnu flere hvis / ellers udsagn.

Desuden ville al logik for hver af staterne være spredt på alle metoder. Nu er det her, hvor statsmønsteret kan anses for at bruge. Takket være statens designmønster kan vi indkapsle logikken i dedikerede klasser, anvende det enkelte ansvarsprincip og det åbne / lukkede princip, have renere og mere vedligeholdelig kode.

3. UML-diagram

I UML-diagrammet ser vi det Sammenhæng klasse har en tilknyttet Stat som vil ændre sig under programudførelsen.

Vores sammenhæng vil delegere adfærd til statsimplementeringen. Med andre ord vil alle indgående anmodninger blive håndteret af den konkrete implementering af staten.

Vi ser, at logikken er adskilt, og det er simpelt at tilføje nye stater - det kommer til at tilføje en anden Stat implementering, hvis det er nødvendigt.

4. Implementering

Lad os designe vores applikation. Som allerede nævnt kan pakken bestilles, leveres og modtages, derfor har vi tre stater og kontekstklassen.

Lad os først definere vores kontekst, det bliver en Pakke klasse:

public class Package {private PackageState state = new OrderedState (); // getter, setter public void previousState () {state.prev (this); } offentlig ugyldighed nextState () {state.next (dette); } offentlig ugyldig printStatus () {state.printStatus (); }}

Som vi kan se, indeholder den en reference til administration af staten, meddelelse previousState (), nextState () og printStatus () metoder, hvor vi delegerer jobbet til statsobjektet. Staterne vil være knyttet til hinanden og hver stat vil sætte en anden baseret på det her reference videregivet til begge metoder.

Klienten vil interagere med Pakke klasse, men alligevel behøver han ikke beskæftige sig med indstilling af staterne, alt hvad klienten skal gøre er at gå til den næste eller forrige tilstand.

Dernæst skal vi have PackageState som har tre metoder med følgende underskrifter:

offentlig grænseflade PackageState {ugyldig næste (pakke pkg); ugyldig tidligere (pakke pkg); ugyldig printStatus (); }

Denne grænseflade implementeres af hver konkret tilstandsklasse.

Den første konkrete tilstand vil være OrderedState:

offentlig klasse OrderedState implementerer PackageState {@Override public void next (Package pkg) {pkg.setState (new DeliveredState ()); } @Override public void prev (Package pkg) {System.out.println ("Pakken er i sin rodtilstand."); } @ Override offentlig ugyldig printStatus () {System.out.println ("Pakke bestilt, endnu ikke leveret til kontoret."); }}

Her peger vi på den næste tilstand, der vil opstå, efter at pakken er bestilt. Den ordnede tilstand er vores rodtilstand, og vi markerer den eksplicit. Vi kan se i begge metoder, hvordan overgangen mellem stater håndteres.

Lad os se på DeliveredState klasse:

offentlig klasse DeliveredState implementerer PackageState {@Override public void next (pakke pkg) {pkg.setState (ny ReceState ()); } @ Override public void prev (Package pkg) {pkg.setState (new OrderedState ()); } @Override public void printStatus () {System.out.println ("Pakke leveret til postkontoret, endnu ikke modtaget."); }}

Igen ser vi sammenhængen mellem staterne. Pakken ændrer sin tilstand fra bestilt til leveret, meddelelsen i printStatus () ændringer også.

Den sidste status er Modtaget stat:

offentlig klasse Receivate implementerer PackageState {@Override public void next (pakke pkg) {System.out.println ("Denne pakke er allerede modtaget af en klient."); } @ Override public void prev (Package pkg) {pkg.setState (new DeliveredState ()); }}

Det er her, vi når den sidste tilstand, vi kan kun vende tilbage til den tidligere tilstand.

Vi ser allerede, at der er noget udbytte, da den ene stat kender til den anden. Vi gør dem tæt sammenkoblede.

5. Testning

Lad os se, hvordan implementeringen opfører sig. Lad os først kontrollere, om installationsovergange fungerer som forventet:

@Test offentlig ugyldighed givenNewPackage_whenPackageReceived_thenStateReceived () {Pakke pkg = ny pakke (); assertThat (pkg.getState (), instanceOf (OrderedState.class)); pkg.nextState (); assertThat (pkg.getState (), instanceOf (DeliveredState.class)); pkg.nextState (); assertThat (pkg.getState (), instanceOf (ReceiverState.class)); }

Kontroller derefter hurtigt, om vores pakke kan flytte tilbage med sin tilstand:

@Test offentlig ugyldighed givenDeliveredPackage_whenPrevState_thenStateOrdered () {Pakke pkg = ny pakke (); pkg.setState (ny DeliveredState ()); pkg.previousState (); assertThat (pkg.getState (), instanceOf (OrderedState.class)); }

Efter det, lad os kontrollere ændre tilstanden og se, hvordan implementeringen af printStatus () metode ændrer implementeringen ved kørsel:

public class StateDemo {public static void main (String [] args) {Package pkg = new Package (); pkg.printStatus (); pkg.nextState (); pkg.printStatus (); pkg.nextState (); pkg.printStatus (); pkg.nextState (); pkg.printStatus (); }}

Dette giver os følgende output:

Pakke bestilt, endnu ikke leveret til kontoret. Pakke leveret til postkontoret, endnu ikke modtaget. Pakken blev modtaget af klienten. Denne pakke er allerede modtaget af en klient. Pakken blev modtaget af klienten.

Da vi har ændret tilstanden i vores kontekst, ændrede adfærden sig, men klassen forbliver den samme. Samt den API, vi bruger.

Også overgangen mellem staterne har fundet sted, vores klasse ændrede sin tilstand og følgelig dens adfærd.

6. Ulemper

Statlig mønster ulempe er udbetalingen ved implementering af overgang mellem staterne. Det gør staten hårdkodet, hvilket generelt er en dårlig praksis.

Men afhængigt af vores behov og krav er det måske eller måske ikke et problem.

7. Stat vs. strategimønster

Begge designmønstre er meget ens, men deres UML-diagram er den samme, med ideen bag dem lidt anderledes.

Først strategimønster definerer en familie af udskiftelige algoritmer. Generelt opnår de det samme mål, men med en anden implementering, for eksempel sortering eller gengivelse af algoritmer.

I tilstandsmønster kan opførslen ændre sig fuldstændigt, baseret på den faktiske tilstand.

Næste, i strategi skal klienten være opmærksom på de mulige strategier til at bruge og ændre dem eksplicit. Mens der i tilstandsmønster er hver stat knyttet til en anden og skaber strømmen som i Finite State Machine.

8. Konklusion

Statens designmønster er fantastisk, når vi vil undgå primitive hvis / ellers udsagn. I stedet for udtræk logikken til separate klasser og lad vores kontekst objekt delegere adfærd til metoderne implementeret i statsklassen. Desuden kan vi udnytte overgangene mellem staterne, hvor en tilstand kan ændre kontekstens tilstand.

Generelt er dette designmønster godt til relativt enkle applikationer, men for en mere avanceret tilgang kan vi se på Spring's State Machine tutorial.

Som sædvanlig er den komplette kode tilgængelig på GitHub-projektet.