Uzorak državnog dizajna u Javi

1. Pregled

U ovom uputstvu predstavit ćemo jedan od obrazaca ponašanja GoF-a - državni obrazac.

Isprva ćemo dati pregled njegove svrhe i objasniti problem koji pokušava riješiti. Zatim ćemo pogledati UML dijagram države i provedbu praktičnog primjera.

2. Uzorak državnog dizajna

Glavna ideja državnog obrasca je da dopustiti objektu promjenu ponašanja bez promjene klase. Također, primjenjujući ga, kôd bi trebao ostati čišći bez mnogo if / else izjava.

Zamislite da imamo paket koji se šalje pošti, sam paket se može naručiti, dostaviti pošti i na kraju primiti klijent. Sada, ovisno o stvarnom stanju, želimo ispisati njegov status isporuke.

Najjednostavniji pristup bio bi dodati neke logičke zastavice i primijeniti jednostavne if / else izraze unutar svake od naših metoda u klasi. To u jednostavnom scenariju neće puno zakomplicirati. Međutim, to bi moglo zakomplicirati i zagađivati ​​naš kod kad dobijemo da obradi više država što će rezultirati još više if / else izjava.

Osim toga, sva bi se logika za svaku državu proširila na sve metode. Sad se tu može smatrati da se koristi državni obrazac. Zahvaljujući državnom uzorku dizajna, možemo uvrstiti logiku u namjenske satove, primijeniti princip jedinstvene odgovornosti i otvoreni / zatvoreni princip, imati čistiji i održiviji kôd.

3. UML dijagram

Na UML dijagramu to vidimo Kontekst razred ima pridruženu država što će se promijeniti tijekom izvršavanja programa.

Naš kontekst ide delegirati ponašanje državnoj provedbi. Drugim riječima, svi dolazni zahtjevi rješavat će se konkretnom provedbom države.

Vidimo da je logika odvojena, a dodavanje novih stanja je jednostavno - svodi se na dodavanje novih država provedba ako je potrebno.

4. Provedba

Dizajnirajmo našu aplikaciju. Kao što je već spomenuto, paket se može naručiti, isporučiti i primiti, stoga ćemo imati tri stanja i kontekst klasu.

Prvo, definirajmo naš kontekst, to će biti Paket razred:

javna klasa Package {private PackageState state = new OrderedState (); // getter, setter javna praznina previousState () {state.prev (this); } javna void nextState () {state.next (this); } javna void printStatus () {state.printStatus (); }}

Kao što vidimo, sadrži referencu za upravljanje državom, napomena previousState (), nextState () i printStatus () metode gdje posao delegiramo na državni objekt. Države će biti međusobno povezane i svaka će država postaviti drugu na temelju ovaj referenca prešao na obje metode.

Klijent će komunicirati s Paket klase, ali on se neće morati baviti postavljanjem stanja, sve što klijent mora učiniti je prijeći u sljedeće ili prethodno stanje.

Dalje, imat ćemo PackageState koja ima tri metode sa sljedećim potpisima:

javno sučelje PackageState {void next (Package pkg); void prev (Pakiranje kg); void printStatus (); }

Ovo će sučelje implementirati svaka konkretna klasa stanja.

Prvo konkretno stanje bit će NaređenoDržava:

javna klasa OrderedState implementira PackageState {@Override public void next (Package pkg) {pkg.setState (new DeliveredState ()); } @Override public void prev (Paket pkg) {System.out.println ("Paket je u izvornom stanju."); } @Override public void printStatus () {System.out.println ("Paket je naručen, još nije dostavljen u ured."); }}

Ovdje ukazujemo na sljedeće stanje koje će se dogoditi nakon narudžbe paketa. Uređeno stanje je naše korijensko stanje i mi ga eksplicitno označavamo. U obje metode možemo vidjeti kako se rješava prijelaz između stanja.

Pogledajmo DeliveredState razred:

javna klasa DeliveredState implementira PackageState {@Override public void next (Package pkg) {pkg.setState (new ReceivedState ()); } @Override public void prev (Package pkg) {pkg.setState (new OrderedState ()); } @Override public void printStatus () {System.out.println ("Paket isporučen pošti, još nije primljen."); }}

Opet vidimo povezanost država. Paket mijenja svoje stanje iz naručenog u isporučeno, poruka u printStatus () promjene također.

Posljednji status je ReceivedState:

javna klasa ReceivedState implementira PackageState {@Override public void next (Package pkg) {System.out.println ("Ovaj paket klijent je već primio."); } @Override public void prev (Package pkg) {pkg.setState (new DeliveredState ()); }}

Tu dolazimo do posljednjeg stanja, možemo se samo vratiti u prethodno stanje.

Već vidimo da postoji određena isplata budući da jedna država zna za drugu. Čvrsto smo povezani.

5. Ispitivanje

Pogledajmo kako se ponaša implementacija. Prvo provjerimo rade li prijelazi postavljanja kako se očekivalo:

@Test javna praznina givenNewPackage_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 (ReceivedState.class)); }

Zatim, brzo provjerite može li se naš paket vratiti natrag sa svojim stanjem:

@Test javna praznina givenDeliveredPackage_whenPrevState_thenStateOrdered () {Paket pkg = novi paket (); pkg.setState (novo isporučeno stanje ()); pkg.previousState (); assertThat (pkg.getState (), instanceOf (OrderedState.class)); }

Nakon toga, provjerimo promjenu stanja i vidimo kako će se implementirati printStatus () metoda mijenja njezinu implementaciju u vrijeme izvođenja:

javna klasa 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 (); }}

To će nam dati sljedeći izlaz:

Paket je naručen, još nije dostavljen u ured. Paket je dostavljen pošti, još nije primljen. Paket je primio klijent. Klijent je već primio ovaj paket. Paket je primio klijent.

Kako smo mijenjali stanje u našem kontekstu, ponašanje se mijenjalo, ali klasa ostaje ista. Kao i API koji koristimo.

Također, dogodio se prijelaz između država, naša je klasa promijenila svoje stanje i posljedično ponašanje.

6. Loše strane

Nedostatak državnog uzorka je isplativost pri provođenju tranzicije između država. To državu čini čvrsto kodiranom, što je općenito loša praksa.

No, ovisno o našim potrebama i zahtjevima, to bi moglo biti, a ne mora biti problem.

7. Uzorak države protiv strategije

Oba su dizajnerska uzorka vrlo slična, ali njihov UML dijagram je isti, a ideja se iza njih malo razlikuje.

Prvo, obrazac strategije definira obitelj zamjenjivih algoritama. Općenito postižu isti cilj, ali s drugačijom implementacijom, na primjer algoritmima za sortiranje ili prikazivanje.

U uzorku stanja, ponašanje bi se moglo potpuno promijeniti, na temelju stvarnog stanja.

Sljedeći, u strategiji, klijent mora biti svjestan mogućih strategija da ih eksplicitno koristi i mijenja. Dok je u obrascu stanja svako stanje povezano s drugim i stvara protok kao u stroju konačnih stanja.

8. Zaključak

Uzorak državnog dizajna izvrstan je kad to želimo izbjegavajte primitivne if / else izjave. Umjesto toga, mi izvući logiku u odvojene razrede i neka naša objekt konteksta delegirati ponašanje metodama provedenim u državnom razredu. Osim toga, možemo iskoristiti prijelaze između država, gdje jedna država može promijeniti stanje konteksta.

Općenito, ovaj obrazac dizajna izvrstan je za relativno jednostavne aplikacije, ali za napredniji pristup možemo pogledati Spring's tutorial State Machine.

Kao i obično, cjeloviti kôd dostupan je na projektu GitHub.