Ett designmönster är en mall som löser ett vanligt återkommande problem inom mjukvarudesign.

Tillståndsmönstret är ett beteendemönster som låter ett objekt ändra sitt beteende när dess interna tillstånd ändras.

Här får du lära dig hur du använder tillståndsmönstret i TypeScript.

Vad är statens mönster?

Tillståndsdesignmönstret är nära relaterat till en finita-tillståndsmaskin, som beskriver ett program som finns i en ändlig antal stater vid varje givet ögonblick och beter sig olika inom varje stat.

Det finns begränsade, förutbestämda regler – övergångar – som styr de andra staterna som varje stat kan byta till.

För sammanhanget, i en onlinebutik, om en kunds shoppingorder har "levererats", kan den inte "avbrytas" eftersom den redan har "levererats". "Levererad" och "Avbruten" är ändliga tillstånd för beställningen, och beställningen kommer att bete sig annorlunda baserat på dess tillstånd.

Statsmönstret skapar en klass för varje möjligt tillstånd, med tillståndsspecifikt beteende som ingår i varje klass.

instagram viewer

Ett exempel på tillståndsbaserad applikation

Anta till exempel att du skapar ett program som spårar tillstånden för en artikel för ett förlag. En artikel kan antingen vara i väntan på godkännande, utarbetad av en författare, redigerad av en redaktör eller publicerad. Dessa är de finita tillstånden för en artikel som ska publiceras; inom varje unikt tillstånd beter sig artikeln annorlunda.

Du kan visualisera de olika tillstånden och övergångarna i artikelapplikationen med tillståndsdiagrammet nedan:

Genom att implementera detta scenario i kod måste du först deklarera ett gränssnitt för artikeln:

gränssnittArtikelgränssnitt{
tonhöjd(): tomhet;
förslag(): tomhet;
redigera(): tomhet;
publicera(): tomhet;
}

Detta gränssnitt kommer att ha alla möjliga tillstånd för applikationen.

Skapa sedan en applikation som implementerar alla gränssnittsmetoder:

// Ansökan
klassArtikelredskapArtikelgränssnitt{
konstruktör() {
detta.showCurrentState();
}

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

offentligtonhöjd(): tomhet{
//...
}

offentligförslag(): tomhet{
//...
}

offentligredigera(): tomhet{
//...
}

offentligpublicera(): tomhet{
//...
}
}

Det privata showCurrentState metod är en verktygsmetod. Denna handledning använder den för att visa vad som händer i varje stat. Det är inte en obligatorisk del av tillståndsmönstret.

Hantera statliga övergångar

Därefter måste du hantera tillståndsövergångarna. Att hantera tillståndsövergången i din applikationsklass skulle kräva många villkorliga uttalanden. Detta skulle resultera i repetitiv kod som är svårare att läsa och underhålla. För att lösa detta problem kan du delegera övergångslogiken för varje stat till sin egen klass.

Innan du skriver varje tillståndsklass bör du skapa en abstrakt basklass för att säkerställa att alla metoder som anropas i ett ogiltigt tillstånd ger ett fel.

Till exempel:

abstraktklassArticleStateredskapArtikelgränssnitt{
pitch(): ArticleState {
kastanyFel("Ogiltig operation: Kan inte utföra uppgiften i nuvarande tillstånd");
}

draft(): ArticleState {
kastanyFel("Ogiltig operation: Kan inte utföra uppgiften i nuvarande tillstånd");
}

edit(): ArticleState {
kastanyFel("Ogiltig operation: Kan inte utföra uppgiften i nuvarande tillstånd");
}

publicera(): ArticleState {
kastanyFel("Ogiltig operation: Kan inte utföra uppgiften i nuvarande tillstånd");
}
}

I basklassen ovan ger varje metod ett fel. Nu måste du åsidosätta varje metod genom att skapa specifika klasser som sträcker sig basklassen för varje stat. Varje specifik klass kommer att innehålla tillståndsspecifik logik.

Varje applikation har ett viloläge, vilket initierar applikationen. I viloläge för denna applikation kommer applikationen att ställas in på förslag stat.

Till exempel:

klassPendingDraftStatesträcker sigArticleState{
pitch(): ArticleState {
lämna tillbakany DraftState();
}
}

De tonhöjd metoden i klassen ovan initierar applikationen genom att ställa in det aktuella tillståndet till DraftState.

Åsidosätt sedan resten av metoderna så här:

klassDraftStatesträcker sigArticleState{
draft(): ArticleState {
lämna tillbakany EditingState();
}
}

Denna kod åsidosätter förslag metod och returnerar en instans av EditingState.

klassEditingStatesträcker sigArticleState{
edit(): ArticleState {
lämna tillbakany PublishedState();
}
}

Kodblocket ovan åsidosätter redigera metod och returnerar en instans av PubliceradStat.

klassPubliceradStatsträcker sigArticleState{
publicera(): ArticleState {
lämna tillbakany PendingDraftState();
}
}

Kodblocket ovan åsidosätter publicera metod och sätter applikationen tillbaka i viloläge, PendingDraftState.

Sedan måste du tillåta applikationen att ändra sitt tillstånd internt genom att referera till det aktuella tillståndet genom en privat variabel. Du kan göra detta genom att initiera viloläget i din applikationsklass och lagra värdet i en privat variabel:

privat state: ArticleState = ny PendingDraftState();

Uppdatera sedan showCurrentState metod för att skriva ut det aktuella tillståndsvärdet:

privatshowCurrentState(): tomhet{
trösta.logga(detta.stat);
}

De showCurrentState metod loggar programmets aktuella tillstånd till konsolen.

Tilldela slutligen om den privata variabeln till den aktuella tillståndsinstansen i var och en av ditt programs metoder.

Uppdatera till exempel dina applikationer tonhöjd metod till kodblocket nedan:

offentligtonhöjd(): tomhet{
detta.stat = detta.state.pitch();
detta.showCurrentState();
}

I kodblocket ovan visas tonhöjd metoden ändrar tillståndet från nuvarande tillstånd till tonhöjdstillstånd.

På liknande sätt kommer alla andra metoder att ändra tillståndet från det aktuella applikationstillståndet till sina respektive tillstånd.

Uppdatera dina ansökningsmetoder till kodblocken nedan:

De förslag metod:

offentligförslag(): tomhet{
detta.stat = detta.state.utkast();
detta.showCurrentState();
}

De redigera metod:

offentligredigera(): tomhet{
detta.stat = detta.state.edit();
detta.showCurrentState();
}

Och den publicera metod:

offentligpublicera(): tomhet{
detta.stat = detta.state.publicera();
detta.showCurrentState();
}

Använda den färdiga applikationen

Din färdiga applikationsklass bör likna kodblocket nedan:

// Ansökan
klassArtikelredskapArtikelgränssnitt{
privat state: ArticleState = ny PendingDraftState();

konstruktör() {
detta.showCurrentState();
}

privatshowCurrentState(): tomhet{
trösta.logga(detta.stat);
}

offentligtonhöjd(): tomhet{
detta.stat = detta.state.pitch();
detta.showCurrentState();
}

offentligförslag(): tomhet{
detta.stat = detta.state.utkast();
detta.showCurrentState();
}

offentligredigera(): tomhet{
detta.stat = detta.state.edit();
detta.showCurrentState();
}

offentligpublicera(): tomhet{
detta.stat = detta.state.publicera();
detta.showCurrentState();
}
}

Du kan testa tillståndsövergångarna genom att anropa metoderna i rätt ordning. Till exempel:

konst docs = ny Artikel(); // PendingDraftState: {}

docs.pitch(); // DraftState: {}
docs.draft(); // EditingState: {}
docs.edit(); // Publicerad tillstånd: {}
docs.publish(); // PendingDraftState: {}

Kodblocket ovan fungerar eftersom programmets tillstånd har övergått på lämpligt sätt.

Om du försöker ändra tillståndet på ett sätt som inte är tillåtet, till exempel från tonhöjdsläge till redigeringstillstånd, kommer programmet att ge ett felmeddelande:

konst docs = ny Artikel(); // PendingDraftState: {}
docs.pitch() // DraftState: {}
docs.edit() // Ogiltig operation: Kan inte utföra uppgiften i nuvarande tillstånd

Du bör endast använda detta mönster när:

  • Du skapar ett objekt som beter sig annorlunda beroende på dess nuvarande tillstånd.
  • Objektet har många tillstånd.
  • Det tillståndsspecifika beteendet ändras ofta.

Fördelar och avvägningar med det statliga mönstret

Detta mönster eliminerar skrymmande villkorliga uttalanden och upprätthåller det enda ansvaret och öppna/stängda principerna. Men det kan vara överdrivet om applikationen har få tillstånd eller om dess tillstånd inte är särskilt dynamiska.