我们经常使用简单的枚举来表示我们实体上的状态。当我们引入主要取决于状态的行为,或者状态转换必须遵守某些业务规则时,就会出现问题。
采用以下示例(使用枚举来表示状态):
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
}
}
你可以看到,当我们为Reject,Complete,Remove等添加方法时,这个类很快会变得非常冗长。
相反,我们可以引入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;
}
}
这使得在状态之间转换变得容易,根据当前状态执行逻辑或在需要时添加新状态:
// transition state
vacancy.State.Approve();
// conditional
model.ShowRejectButton = vacancy.State.CanReject();
这种封装看起来更清晰,但是如果有足够的状态,这些也会变得非常冗长。我读了Greg Young's post on State Pattern Misuse,建议使用多态(因此我会有ApprovedVacancy,UnapprovedVacancy等类),但是看不出这对我有什么帮助。
我应该将这种状态转换委托给域服务,还是我在这种情况下使用状态模式是正确的?
答案 0 :(得分:5)
要回答您的问题,您不应将其委托给域名服务,并且您对状态模式的使用几乎是正确的。
详细说明,维护对象状态的责任属于该对象,因此将其降级为域服务会导致贫血模型。这并不是说国家修改的责任不能通过使用其他模式来授权,但这应该对对象的消费者是透明的。
这导致我使用State模式。在大多数情况下,您正确使用该模式。你偏离了一点的部分是你的德米特法则违规。您的对象的使用者不应该进入您的对象并调用其状态的方法(例如vacancy.State.CanReject()
),而是您的对象应该将此调用委托给State对象(例如vacancy.CanReject()
- > bool CanReject() { return _state.CanReject(); }
)。对象的使用者不必知道您甚至使用State模式。
要对您引用的文章发表评论,状态模式依赖于多态,因为它是促进机制。封装State实现的对象能够将调用委托给当前分配的任何实现,无论是什么都不做,抛出异常或执行某些操作。此外,虽然通过使用状态模式(或任何其他模式)肯定可能导致Liskov替换原则违规,但这不是由对象可能抛出异常的事实决定的,而是由对对象的修改可以根据现有代码制作(阅读this以进一步讨论)。