2012-04-04 84 views
5

我們經常使用簡單的枚舉來表示我們的實體狀態。當我們引入很大程度上取決於國家的行爲時,或者國家轉型必須遵守某些業務規則時,問題就出現了。狀態模式和域驅動設計

看看下面的例子(使用一個枚舉來表示狀態):

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 
    } 
} 

你可以看到,這個類將很快成爲我們添加的拒絕方法,完全相當冗長,刪除等

相反,我們可以引入State模式,它允許我們封裝每個州作爲一個對象:

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; 
    } 
} 

這可以很容易地過渡betwee n種狀態,進行基於當前狀態的邏輯或者,如果我們需要增加新規定:

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

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

這種封裝似乎更清潔,但給予足夠的狀態,這些也可以變得非常冗長。我讀Greg Young's post on State Pattern Misuse,建議使用多態性代替(所以我會有ApprovedVacancy,UnapprovedVacancy等類),但不能看到這將如何幫助我。

我應該將這種狀態轉換委託給域服務還是我在這種情況下使用狀態模式是正確的?

回答

5

要回答你的問題,你不應該把它委託給域服務,並且你對狀態模式的使用幾乎是正確的。

詳細說明,維護對象狀態的責任屬於該對象,因此將其歸爲域服務會導致貧血模型。這並不是說國家修改的責任不能通過使用其他模式進行授權,但是這對於對象的消費者應該是透明的。

這導致我使用狀態模式。大部分情況下,您正在正確使用該模式。在你違反德米特法的情況下,你偏離的一部分。你的對象的消費者不應該到達你的對象並調用它的狀態方法(例如vacancy.State.CanReject()),而是你的對象應該把這個調用委託給狀態對象(例如vacancy.CanReject() - > bool CanReject(){return _state.CanReject();})。你的對象的消費者不應該知道你甚至使用了狀態模式。

要評論您所引用的文章,狀態模式依賴於多態性,因爲它是促進機制。封裝State實現的對象能夠將調用委託給當前分配的任何實現,無論這種實現是什麼都不做,拋出異常或執行某些操作。此外,儘管通過使用狀態模式(或任何其他模式)違反Liskov替換原則當然是可能的,但這不是由對象可能拋出異常的事實決定的,而是由對對象的修改可以根據現有的代碼製作(閱讀this進一步討論)。

+0

如果消費者不應該直接改變狀態,這意味着我最終將爲我的實體上的每個狀態轉換結束。那麼我在這裏獲得了什麼? – 2012-04-04 15:26:11

+2

您可以獲得封裝並且不存在大量的if語句:)以下是正式的好處:1.它針對不同狀態本地化特定於狀態的行爲和分區行爲。它使狀態轉換顯式化。 3.狀態對象可以共享。 – 2012-04-04 15:58:45

+0

謝謝。最後一個問題 - 我們堅持空缺到文檔存儲(RavenDB)。你會建議持久化一個用於加載相關狀態對象的枚舉嗎?或者只是存儲整個狀態對象 – 2012-04-04 16:12:33