2012-04-04 12 views
5

Wir verwenden oft einfache Enumerationen, um einen Zustand auf unseren Entitäten darzustellen. Das Problem tritt auf, wenn wir Verhaltensweisen einführen, die weitgehend vom Staat abhängen oder bei denen Zustandsübergänge bestimmte Geschäftsregeln einhalten müssen.State Pattern und Domain Driven Design

Nehmen Sie das folgende Beispiel (das eine Aufzählung verwendet Zustand darzustellen):

public class Vacancy { 

    private VacancyState currentState; 

    public void Approve() { 
     if (CanBeApproved()) { 
      currentState.Approve(); 
     } 
    } 

    public bool CanBeApproved() { 
     return currentState == VacancyState.Unapproved 
      || currentState == VacancyState.Removed 
    } 

    private enum VacancyState { 
     Unapproved, 
     Approved, 
     Rejected, 
     Completed, 
     Removed 
    } 
} 

Sie können sehen, dass diese Klasse bald recht ausführliche werden wird, wie wir Methoden hinzufügen für Abweisen, Complete, entfernen usw.

Stattdessen können wir den Staat Muster einführen, die uns jeden Zustand als Objekt zu kapseln erlaubt:

public abstract class VacancyState { 

    protected Vacancy vacancy; 

    public VacancyState(Vacancy vacancy) { 
     this.vacancy = vacancy; 
    } 

    public abstract void Approve(); 
    // public abstract void Unapprove(); 
    // public abstract void Reject(); 
    // etc. 

    public virtual bool CanApprove() { 
     return false; 
    } 
} 

public abstract class UnapprovedState : VacancyState { 

    public UnapprovedState(vacancy) : base(vacancy) { } 

    public override void Approve() { 
     vacancy.State = new ApprovedState(vacancy); 
    } 

    public override bool CanApprove() { 
     return true; 
    } 
} 

Dies macht es einfach zwische den Übergang n Staaten, führen Logik basierend auf dem aktuellen Stand oder neue Staaten hinzu, wenn wir brauchen, um:

// transition state 
vacancy.State.Approve(); 

// conditional 
model.ShowRejectButton = vacancy.State.CanReject(); 

Diese Kapselung scheint saubere, aber genug, um Zustände gegeben, auch diese sehr ausführlich werden können. Ich lese Greg Young's post on State Pattern Misuse, was vorschlägt, stattdessen Polymorphismus zu verwenden (also hätte ich ApprovedVacancy, UnapprovedVacancy usw. Klassen), aber kann nicht sehen, wie mir das helfen wird.

Sollte ich solche Zustandsübergänge an einen Domain-Service delegieren oder ist meine Verwendung des State-Pattern in dieser Situation korrekt?

Antwort

5

Um Ihre Frage zu beantworten, sollten Sie diese nicht an einen Domain-Service delegieren und Ihre Verwendung des State-Patterns ist fast korrekt.

Um zu erklären, die Verantwortung für die Aufrechterhaltung des Zustands eines Objekts gehört zu diesem Objekt, so dass dies zu einem Domain-Service verbietet führt zu anämischen Modellen. Das soll nicht heißen, dass die Verantwortung für staatliche Modifikationen nicht durch die Verwendung anderer Muster delegiert werden kann, sondern dies sollte für den Konsumenten des Objekts transparent sein.

Das führt mich zu Ihrer Verwendung des Zustandsmusters. In den meisten Fällen verwenden Sie das Muster korrekt. Die eine Portion, in der du dich ein wenig verirrst, ist in deinen Demeter-Gesetzesverstößen enthalten. Der Konsument Ihres Objekts sollte nicht in Ihr Objekt greifen und Methoden auf seinen Zustand aufrufen (zB vacancy.State.CanReject()), sondern Ihr Objekt sollte diesen Aufruf an das State-Objekt delegieren (zB vacancy.CanReject() - > bool CanReject() {return_state.CanReject();}). Der Verbraucher Ihres Objekts sollte nicht wissen müssen, dass Sie sogar das Zustandsmuster verwenden.

Um den Artikel zu kommentieren, auf den Sie verwiesen haben, beruht das Zustandsmuster auf Polymorphismus, da es den Mechanismus erleichtert. Das Objekt, das eine Statusimplementierung kapselt, kann einen Aufruf an die aktuell zugewiesene Implementierung delegieren, sei es etwas, das nichts tut, eine Ausnahme auslöst oder eine Aktion ausführt. Auch wenn es natürlich möglich ist, durch Verwendung des Zustandsmusters (oder eines anderen Musters) eine Verletzung des Lis- kov-Substitutionsprinzips zu verursachen, wird dies nicht durch die Tatsache bestimmt, dass das Objekt eine Ausnahme auslösen kann, sondern durch Modifikationen an einem Objekt kann in Anbetracht der bestehenden Code gemacht werden (lesen Sie this für weitere Diskussion).

+0

Wenn der Verbraucher den Status nicht direkt ändern sollte, bedeutet dies, dass ich mit einer Methode für jeden Statusübergang auf meiner Entität enden werde. Was kann ich hier wirklich gewinnen? –

+2

Sie erhalten Kapselung und das Fehlen einer Reihe von if-Anweisungen :) Hier ist die offizielle Vorteile: 1. Es lokalisiert das zustandsspezifische Verhalten und Partitionen Verhalten für verschiedene Staaten. 2. Es macht Zustandsübergänge explizit. 3. Zustandsobjekte können geteilt werden. –

+0

Danke.Eine abschließende Frage - wir bestehen auf Stellenausschreibungen in einen Dokumentenspeicher (RavenDB). Würden Sie vorschlagen, eine Enumeration beizubehalten, die zum Laden des relevanten Zustandsobjekts verwendet wird oder nur das gesamte Zustandsobjekt speichert? –

Verwandte Themen