State Design Pattern in Java

1. Overzicht

In deze tutorial introduceren we een van de gedragspatronen voor GoF-ontwerpen: het State-patroon.

Eerst geven we een overzicht van het doel en leggen we het probleem uit dat het probeert op te lossen. Vervolgens bekijken we het UML-diagram van de staat en de implementatie van het praktische voorbeeld.

2. Staat ontwerppatroon

Het belangrijkste idee van het staatspatroon is om laat het object zijn gedrag veranderen zonder zijn klasse te veranderen. Door het te implementeren, zou de code ook schoner moeten blijven zonder veel if / else-instructies.

Stel dat we een pakket hebben dat naar een postkantoor wordt gestuurd, het pakket zelf kan worden besteld, vervolgens afgeleverd op een postkantoor en uiteindelijk door een klant worden ontvangen. Nu willen we, afhankelijk van de huidige staat, de bezorgstatus afdrukken.

De eenvoudigste benadering zou zijn om enkele booleaanse vlaggen toe te voegen en eenvoudige if / else-instructies toe te passen binnen elk van onze methoden in de klasse. Dat zal het in een eenvoudig scenario niet veel ingewikkelder maken. Het kan onze code echter bemoeilijken en vervuilen als we meer staten te verwerken krijgen, wat zal resulteren in nog meer if / else-statements.

Bovendien zou alle logica voor elk van de staten over alle methoden worden verspreid. Dit is waar het State-patroon zou kunnen worden gebruikt. Dankzij het State-ontwerppatroon kunnen we de logica inkapselen in speciale klassen, het Single Responsibility-principe en het Open / Closed-principe toepassen, en schonere en beter te onderhouden code hebben.

3. UML-diagram

In het UML-diagram zien we dat Context klasse heeft een bijbehorend Staat die gaat veranderen tijdens de uitvoering van het programma.

Onze context zal delegeer het gedrag aan de staatsimplementatie. Met andere woorden, alle inkomende verzoeken worden afgehandeld door de concrete uitvoering van de staat.

We zien dat logica gescheiden is en dat het toevoegen van nieuwe toestanden eenvoudig is - het komt erop aan nog een toe te voegen Staat implementatie indien nodig.

4. Implementatie

Laten we onze applicatie ontwerpen. Zoals eerder vermeld, kan het pakket worden besteld, bezorgd en ontvangen, daarom hebben we drie staten en de contextklasse.

Laten we eerst onze context definiëren, dat wordt een Pakket klasse:

openbare klasse Pakket {privé PackageState staat = nieuwe OrderedState (); // getter, setter public void previousState () {state.prev (this); } public void nextState () {state.next (dit); } openbare ongeldige printStatus () {state.printStatus (); }}

Zoals we kunnen zien, bevat het een referentie voor het beheer van de staat, let op previousState (), nextState () en printStatus () methoden waarbij we de taak delegeren aan het statusobject. De staten zullen met elkaar worden verbonden en elke staat zal een andere instellen op basis van dit referentie doorgegeven aan beide methoden.

De klant zal communiceren met het Pakket klasse, maar hij hoeft zich niet bezig te houden met het instellen van de staten, het enige dat de klant hoeft te doen is naar de volgende of vorige staat te gaan.

Vervolgens hebben we de PackageState die drie methoden heeft met de volgende handtekeningen:

openbare interface PackageState {void next (pakketpakket); void prev (pakket pkg); leegte printStatus (); }

Deze interface wordt geïmplementeerd door elke concrete state-klasse.

De eerste concrete toestand zal zijn OrderedState:

openbare klasse OrderedState implementeert PackageState {@Override public void next (pakketpakket) {pkg.setState (nieuwe DeliveredState ()); } @Override public void prev (Pakketpakket) {System.out.println ("Het pakket bevindt zich in de rootstatus."); } @Override public void printStatus () {System.out.println ("Pakket besteld, nog niet op kantoor afgeleverd."); }}

Hier wijzen we naar de volgende status die zal optreden nadat het pakket is besteld. De geordende staat is onze rootstatus en we markeren deze expliciet. We kunnen in beide methoden zien hoe met de overgang tussen staten wordt omgegaan.

Laten we eens kijken naar de DeliveredState klasse:

public class DeliveredState implementeert PackageState {@Override public void next (Pakket pakket) {pkg.setState (nieuwe ReceivedState ()); } @Override public void vorige (Pakketpakket) {pkg.setState (new OrderedState ()); } @Override public void printStatus () {System.out.println ("Pakket bezorgd op postkantoor, nog niet ontvangen."); }}

Opnieuw zien we de koppeling tussen de staten. Het pakket verandert van status van besteld naar afgeleverd, het bericht in de printStatus () verandert ook.

De laatste status is ReceivedState:

openbare klasse ReceivedState implementeert PackageState {@Override public void next (Pakketpakket) {System.out.println ("Dit pakket is al ontvangen door een klant."); } @Override public void vorige (Pakketpakket) {pkg.setState (new DeliveredState ()); }}

Dit is waar we de laatste staat bereiken, we kunnen alleen terugdraaien naar de vorige staat.

We zien al dat er enige uitbetaling is, aangezien de ene staat van de andere weet. We maken ze nauw met elkaar verbonden.

5. Testen

Laten we eens kijken hoe de implementatie zich gedraagt. Laten we eerst controleren of de setup-overgangen werken zoals verwacht:

@Test openbare ongeldig gegevenNewPackage_whenPackageReceived_thenStateReceived () {Pakket pkg = nieuw pakket (); assertThat (pkg.getState (), instanceOf (OrderedState.class)); pkg.nextState (); assertThat (pkg.getState (), instanceOf (DeliveredState.class)); pkg.nextState (); assertThat (pkg.getState (), instanceOf (ReceivedState.class)); }

Controleer vervolgens snel of ons pakket terug kan gaan met zijn staat:

@Test openbare ongeldig gegevenDeliveredPackage_whenPrevState_thenStateOrdered () {Pakket pkg = nieuw pakket (); pkg.setState (nieuwe DeliveredState ()); pkg.previousState (); assertThat (pkg.getState (), instanceOf (OrderedState.class)); }

Laten we daarna de status wijzigen en kijken hoe de implementatie van printStatus () methode verandert zijn implementatie tijdens runtime:

openbare klasse StateDemo {openbare statische leegte hoofd (String [] args) {Pakket pkg = nieuw pakket (); pkg.printStatus (); pkg.nextState (); pkg.printStatus (); pkg.nextState (); pkg.printStatus (); pkg.nextState (); pkg.printStatus (); }}

Dit geeft ons de volgende output:

Pakket besteld, nog niet op kantoor bezorgd. Pakket bezorgd op het postkantoor, nog niet ontvangen. Pakket is ontvangen door klant. Dit pakket is al ontvangen door een klant. Pakket is ontvangen door klant.

Terwijl we de toestand van onze context veranderden, veranderde het gedrag, maar de klasse blijft hetzelfde. Evenals de API die we gebruiken.

Ook heeft de overgang tussen de staten plaatsgevonden, onze klasse heeft haar toestand veranderd en dientengevolge haar gedrag.

6. Minpunten

Het nadeel van het statuspatroon is de uitbetaling bij het implementeren van een overgang tussen de staten. Dat maakt de staat hardcoded, wat in het algemeen een slechte gewoonte is.

Maar, afhankelijk van onze behoeften en vereisten, kan dat een probleem zijn.

7. Staat vs. strategiepatroon

Beide ontwerppatronen lijken erg op elkaar, maar hun UML-diagram is hetzelfde, met het idee erachter iets anders.

Eerst de strategiepatroon definieert een familie van uitwisselbare algoritmen. Over het algemeen bereiken ze hetzelfde doel, maar met een andere implementatie, bijvoorbeeld algoritmen voor sorteren of renderen.

In een toestandspatroon kan het gedrag volledig veranderen, gebaseerd op de werkelijke staat.

De volgende, in strategie moet de cliënt zich bewust zijn van de mogelijke strategieën om deze expliciet te gebruiken en te veranderen. Terwijl in een staatspatroon elke staat aan een andere is gekoppeld en de stroom creëert zoals in Finite State Machine.

8. Conclusie

Het staatsontwerppatroon is geweldig als we dat willen vermijd primitieve if / else-statements. In plaats daarvan, wij extraheer de logica om klassen te scheiden en laat onze context-object delegeert het gedrag naar de methoden die zijn geïmplementeerd in de klasse State. Bovendien kunnen we gebruikmaken van de overgangen tussen de staten, waarbij één staat de toestand van de context kan veranderen.

Over het algemeen is dit ontwerppatroon geweldig voor relatief eenvoudige toepassingen, maar voor een meer geavanceerde benadering kunnen we de State Machine-tutorial van Spring bekijken.

Zoals gewoonlijk is de volledige code beschikbaar op het GitHub-project.


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