Statlig designmønster i Java

1. Oversikt

I denne opplæringen vil vi introdusere et av de atferdsmessige GoF-designmønstrene - tilstandsmønsteret.

Først vil vi gi en oversikt over formålet og forklare problemet den prøver å løse. Deretter ser vi på statens UML-diagram og implementering av det praktiske eksemplet.

2. Statlig mønster

Hovedideen med statlig mønster er å tillate objektet å endre oppførsel uten å endre klasse. Ved å implementere den skal koden også forbli renere uten mange hvis / annet uttalelser.

Tenk deg at vi har en pakke som sendes til et postkontor, selve pakken kan bestilles, deretter leveres til et postkontor og til slutt mottas av en klient. Nå, avhengig av den faktiske tilstanden, vil vi skrive ut leveringsstatusen.

Den enkleste tilnærmingen ville være å legge til noen boolske flagg og bruke enkle hvis / annet uttalelser innen hver av metodene våre i klassen. Det vil ikke komplisere det mye i et enkelt scenario. Imidlertid kan det komplisere og forurense koden vår når vi får flere stater til å behandle, noe som vil resultere i enda flere hvis / annet uttalelser.

Dessuten vil all logikk for hver av statene være spredt over alle metoder. Nå er det her staten mønsteret kan anses å bruke. Takket være det statlige designmønsteret kan vi kapsle logikken i dedikerte klasser, anvende Single Responsibility Principle og Open / Closed Principle, ha renere og mer vedlikeholdbar kode.

3. UML-diagram

I UML-diagrammet ser vi det Kontekst klasse har en tilknyttet Stat som kommer til å endres under gjennomføring av programmet.

Vår kontekst skal delegere oppførselen til statsimplementeringen. Med andre ord, alle innkommende forespørsler vil bli håndtert av den konkrete implementeringen av staten.

Vi ser at logikken er atskilt, og det er enkelt å legge til nye stater - det kommer til å legge til en annen Stat implementering om nødvendig.

4. Gjennomføring

La oss designe applikasjonen vår. Som allerede nevnt, kan pakken bestilles, leveres og mottas, derfor skal vi ha tre stater og kontekstklassen.

La oss først definere vår kontekst, det blir en Pakke klasse:

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

Som vi kan se, inneholder den en referanse for administrasjon av staten, merknad previousState (), nextState () og printStatus () metoder der vi delegerer jobben til statsobjektet. Statene vil være knyttet til hverandre og hver stat vil sette en annen basert på dette henvisning overført til begge metodene.

Kunden vil samhandle med Pakke klasse, men likevel vil han ikke ha å gjøre med å sette statene, alt klienten må gjøre er å gå til neste eller forrige tilstand.

Deretter skal vi ha PackageState som har tre metoder med følgende signaturer:

offentlig grensesnitt PackageState {void next (pakke pkg); ugyldig prev (Pakke pkg); ugyldig printStatus (); }

Dette grensesnittet vil bli implementert av hver konkrete tilstandsklasse.

Den første konkrete tilstanden 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 rottilstand."); } @ Override public void printStatus () {System.out.println ("Pakke bestilt, ikke levert til kontoret ennå."); }}

Her peker vi på neste tilstand som vil oppstå etter at pakken er bestilt. Den ordnede tilstanden er vår rottilstand, og vi markerer den eksplisitt. Vi kan se i begge metodene hvordan overgangen mellom stater håndteres.

La oss ta en titt på DeliveredState klasse:

offentlig klasse DeliveredState implementerer PackageState {@Override public void next (Pakke pkg) {pkg.setState (ny Mottatt stat ()); } @ Override public void prev (Package pkg) {pkg.setState (new OrderedState ()); } @ Override public void printStatus () {System.out.println ("Pakke levert til postkontoret, ikke mottatt ennå."); }}

Igjen ser vi koblingen mellom statene. Pakken endrer status fra bestilt til levert, meldingen i printStatus () endres også.

Den siste statusen er Mottatt stat:

offentlig klasse Receiptate implementerer PackageState {@Override public void next (Package pkg) {System.out.println ("Denne pakken er allerede mottatt av en klient."); } @Override public void prev (Package pkg) {pkg.setState (new DeliveredState ()); }}

Det er her vi når den siste tilstanden, vi kan bare gå tilbake til forrige tilstand.

Vi ser allerede at det er noe utbytte siden den ene staten vet om den andre. Vi gjør dem tett koblet.

5. Testing

La oss se hvordan implementeringen oppfører seg. La oss først kontrollere om oppsettoverganger fungerer som forventet:

@Test offentlig ugyldighet gittNewPackage_whenPackageReceived_thenStateReceived () {Package pkg = new Package (); assertThat (pkg.getState (), instanceOf (OrderedState.class)); pkg.nextState (); assertThat (pkg.getState (), instanceOf (DeliveredState.class)); pkg.nextState (); assertThat (pkg.getState (), instanceOf (MottattStat.klasse)); }

Sjekk deretter raskt om pakken vår kan flytte tilbake med tilstanden:

@Test offentlig ugyldighet gittDeliveredPackage_whenPrevState_thenStateOrdered () {Package pkg = new Package (); pkg.setState (ny DeliveredState ()); pkg.previousState (); assertThat (pkg.getState (), instanceOf (OrderedState.class)); }

Etter det, la oss bekrefte å endre tilstanden og se hvordan implementeringen av printStatus () metoden endrer implementeringen ved kjøretid:

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 vil gi oss følgende utdata:

Pakke bestilt, ikke levert til kontoret ennå. Pakke levert til postkontoret, ikke mottatt ennå. Pakken ble mottatt av klienten. Denne pakken er allerede mottatt av en klient. Pakken ble mottatt av klienten.

Ettersom vi har endret tilstanden til konteksten vår, endret oppførselen seg, men klassen forblir den samme. I tillegg til API-en vi bruker.

Overgangen mellom statene har også skjedd, klassen vår endret sin tilstand og følgelig dens oppførsel.

6. Ulemper

Statlig mønster ulempe er gevinsten ved implementering av overgang mellom statene. Det gjør staten hardkodet, noe som er dårlig praksis generelt.

Men avhengig av våre behov og krav, kan det være et problem.

7. Stat vs. strategimønster

Begge designmønstrene er veldig like, men UML-diagrammet deres er det samme, med ideen bak dem litt annerledes.

For det første strategimønster definerer en familie av utskiftbare algoritmer. Generelt oppnår de samme mål, men med en annen implementering, for eksempel sortering eller gjengivelse av algoritmer.

I tilstandsmønster kan oppførselen endres helt, basert på faktisk tilstand.

Neste, i strategi må klienten være klar over mulige strategier for å bruke og endre dem eksplisitt. Mens i tilstandsmønster er hver stat knyttet til en annen og skaper strømmen som i Finite State Machine.

8. Konklusjon

Statens designmønster er flott når vi vil unngå primitive hvis / annet uttalelser. I stedet vi trekk ut logikken for å skille klasser og la vår kontekst objekt delegere oppførselen til metodene implementert i statsklassen. Dessuten kan vi utnytte overgangene mellom statene, der en stat kan endre konteksttilstanden.

Generelt er dette designmønsteret flott for relativt enkle applikasjoner, men for en mer avansert tilnærming kan vi ta en titt på Spring's State Machine tutorial.

Som vanlig er den komplette koden tilgjengelig på GitHub-prosjektet.


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