Et designmønster er en mal som løser et ofte tilbakevendende problem innen programvaredesign.

Tilstandsmønsteret er et atferdsmønster som lar et objekt endre sin oppførsel når dets indre tilstand endres.

Her lærer du hvordan du bruker tilstandsmønsteret i TypeScript.

Hva er statens mønster?

Tilstandsdesignmønsteret er nært knyttet til en finite-state maskin, som beskriver et program som eksisterer i en avgrenset antall stater til enhver tid og oppfører seg forskjellig i hver stat.

Det er begrensede, forhåndsbestemte regler – overganger – som styrer de andre statene som hver stat kan bytte til.

For kontekst, i en nettbutikk, hvis en kundes handleordre har blitt "levert", kan den ikke "kanselleres" fordi den allerede er "levert". "Leveret" og "Kansellert" er endelige tilstander for bestillingen, og bestillingen vil oppføre seg annerledes basert på tilstanden.

Statens mønster oppretter en klasse for hver mulig tilstand, med tilstandsspesifikk oppførsel inneholdt i hver klasse.

Et eksempel på statsbasert applikasjon

Anta for eksempel at du lager en applikasjon som sporer statusen til en artikkel for et forlag. En artikkel kan enten være i påvente av godkjenning, utarbeidet av en skribent, redigert av en redaktør eller publisert. Dette er de endelige tilstandene til en artikkel som skal publiseres; innenfor hver unike tilstand oppfører artikkelen seg annerledes.

Du kan visualisere de forskjellige tilstandene og overgangene til artikkelapplikasjonen med tilstandsdiagrammet nedenfor:

Ved å implementere dette scenariet i kode, må du først deklarere et grensesnitt for artikkelen:

grensesnittArtikkelgrensesnitt{
tonehøyde(): tomrom;
utkast(): tomrom;
redigere(): tomrom;
publisere(): tomrom;
}

Dette grensesnittet vil ha alle mulige tilstander for applikasjonen.

Deretter oppretter du en applikasjon som implementerer alle grensesnittmetodene:

// Applikasjon
klasseArtikkelredskaperArtikkelgrensesnitt{
konstruktør() {
dette.showCurrentState();
}

privatshowCurrentState(): tomrom{
//...
}

offentligtonehøyde(): tomrom{
//...
}

offentligutkast(): tomrom{
//...
}

offentligredigere(): tomrom{
//...
}

offentligpublisere(): tomrom{
//...
}
}

Det private showCurrentState metode er en bruksmetode. Denne opplæringen bruker den til å vise hva som skjer i hver stat. Det er ikke en nødvendig del av statsmønsteret.

Håndtering av statsoverganger

Deretter må du håndtere tilstandsovergangene. Å håndtere tilstandsovergangen i applikasjonsklassen din ville kreve mange betingede uttalelser. Dette vil resultere i repeterende kode som er vanskeligere å lese og vedlikeholde. For å løse dette problemet kan du delegere overgangslogikken for hver stat til sin egen klasse.

Før du skriver hver tilstandsklasse, bør du lage en abstrakt basisklasse for å sikre at enhver metode som kalles i en ugyldig tilstand gir en feil.

For eksempel:

abstraktklasseArtikkelStateredskaperArtikkelgrensesnitt{
pitch(): ArticleState {
kastenyFeil("Ugyldig operasjon: Kan ikke utføre oppgaven i nåværende situasjon");
}

draft(): ArticleState {
kastenyFeil("Ugyldig operasjon: Kan ikke utføre oppgaven i nåværende situasjon");
}

edit(): ArticleState {
kastenyFeil("Ugyldig operasjon: Kan ikke utføre oppgaven i nåværende situasjon");
}

publisere(): ArticleState {
kastenyFeil("Ugyldig operasjon: Kan ikke utføre oppgaven i nåværende situasjon");
}
}

I basisklassen ovenfor gir hver metode en feil. Nå må du overstyre hver metode ved å lage spesifikke klasser som strekker basisklassen for hver stat. Hver spesifikk klasse vil inneholde tilstandsspesifikk logikk.

Hver applikasjon har en inaktiv tilstand, som initialiserer applikasjonen. Inaktiv tilstand for denne applikasjonen vil sette applikasjonen til utkast stat.

For eksempel:

klassePendingDraftStatestrekkerArtikkelState{
pitch(): ArticleState {
komme tilbakeny DraftState();
}
}

De tonehøyde metoden i klassen ovenfor initialiserer applikasjonen ved å sette gjeldende tilstand til DraftState.

Deretter overstyrer du resten av metodene slik:

klasseDraftStatestrekkerArtikkelState{
draft(): ArticleState {
komme tilbakeny EditingState();
}
}

Denne koden overstyrer utkast metode og returnerer en forekomst av Redigeringstilstand.

klasseRedigeringstilstandstrekkerArtikkelState{
edit(): ArticleState {
komme tilbakeny PublishedState();
}
}

Kodeblokken ovenfor overstyrer redigere metode og returnerer en forekomst av UtgittStat.

klasseUtgittStatstrekkerArtikkelState{
publisere(): ArticleState {
komme tilbakeny PendingDraftState();
}
}

Kodeblokken ovenfor overstyrer publisere metode og setter applikasjonen tilbake i inaktiv tilstand, PendingDraftState.

Deretter må du tillate at applikasjonen endrer status internt ved å referere til gjeldende tilstand gjennom en privat variabel. Du kan gjøre dette ved å initialisere inaktiv tilstand i applikasjonsklassen din og lagre verdien til en privat variabel:

privat state: ArticleState = ny PendingDraftState();

Deretter oppdaterer du showCurrentState metode for å skrive ut gjeldende statusverdi:

privatshowCurrentState(): tomrom{
konsoll.Logg(dette.stat);
}

De showCurrentState metoden logger gjeldende status for applikasjonen til konsollen.

Til slutt tilordner du den private variabelen til gjeldende tilstandsforekomst i hver av applikasjonens metoder.

Oppdater for eksempel applikasjonene dine tonehøyde metode til kodeblokken nedenfor:

offentligtonehøyde(): tomrom{
dette.state = dette.state.pitch();
dette.showCurrentState();
}

I kodeblokken ovenfor vises tonehøyde metoden endrer tilstanden fra gjeldende tilstand til tonehøydetilstand.

På samme måte vil alle de andre metodene endre tilstanden fra gjeldende applikasjonstilstand til deres respektive tilstander.

Oppdater søknadsmetodene dine til kodeblokkene nedenfor:

De utkast metode:

offentligutkast(): tomrom{
dette.state = dette.state.utkast();
dette.showCurrentState();
}

De redigere metode:

offentligredigere(): tomrom{
dette.state = dette.state.edit();
dette.showCurrentState();
}

Og publisere metode:

offentligpublisere(): tomrom{
dette.state = dette.state.publisere();
dette.showCurrentState();
}

Bruke den ferdige applikasjonen

Den ferdige søknadsklassen din skal være lik kodeblokken nedenfor:

// Applikasjon
klasseArtikkelredskaperArtikkelgrensesnitt{
privat state: ArticleState = ny PendingDraftState();

konstruktør() {
dette.showCurrentState();
}

privatshowCurrentState(): tomrom{
konsoll.Logg(dette.stat);
}

offentligtonehøyde(): tomrom{
dette.state = dette.state.pitch();
dette.showCurrentState();
}

offentligutkast(): tomrom{
dette.state = dette.state.utkast();
dette.showCurrentState();
}

offentligredigere(): tomrom{
dette.state = dette.state.edit();
dette.showCurrentState();
}

offentligpublisere(): tomrom{
dette.state = dette.state.publisere();
dette.showCurrentState();
}
}

Du kan teste tilstandsovergangene ved å kalle metodene i riktig rekkefølge. For eksempel:

konst dokumenter = ny Artikkel(); // PendingDraftState: {}

docs.pitch(); // DraftState: {}
docs.draft(); // EditingState: {}
docs.edit(); // Publisert tilstand: {}
docs.publish(); // PendingDraftState: {}

Kodeblokken ovenfor fungerer fordi applikasjonens tilstander ble overført på riktig måte.

Hvis du prøver å endre tilstanden på en måte som ikke er tillatt, for eksempel fra tonehøydetilstand til redigeringstilstand, vil applikasjonen gi en feilmelding:

konst dokumenter = ny Artikkel(); // PendingDraftState: {}
docs.pitch() // DraftState: {}
docs.edit() // Ugyldig operasjon: Kan ikke utføre oppgaven i gjeldende tilstand

Du bør bare bruke dette mønsteret når:

  • Du oppretter et objekt som oppfører seg annerledes avhengig av dets nåværende tilstand.
  • Objektet har mange tilstander.
  • Den tilstandsspesifikke oppførselen endres ofte.

Fordeler og avveininger ved statsmønsteret

Dette mønsteret eliminerer store betingede utsagn og opprettholder enkeltansvaret og åpne/lukkede prinsipper. Men det kan være overkill hvis applikasjonen har få tilstander eller tilstandene ikke er spesielt dynamiske.