状态模式和域驱动设计

问题描述:

我们经常使用简单的枚举来表示我们的实体状态。当我们引入很大程度上取决于国家的行为时,或者国家转型必须遵守某些业务规则时,问题就出现了。状态模式和域驱动设计

看看下面的例子(使用一个枚举来表示状态):

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等类),但不能看到这将如何帮助我。

我应该将这种状态转换委托给域服务还是我在这种情况下使用状态模式是正确的?

要回答你的问题,你不应该把它委托给域服务,并且你对状态模式的使用几乎是正确的。

详细说明,维护对象状态的责任属于该对象,因此将其归为域服务会导致贫血模型。这并不是说国家修改的责任不能通过使用其他模式进行授权,但是这对于对象的消费者应该是透明的。

这导致我使用状态模式。大部分情况下,您正在正确使用该模式。在你违反德米特法的情况下,你偏离的一部分。你的对象的消费者不应该到达你的对象并调用它的状态方法(例如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