We often use simple enumerations to represent state on our entities. The problem arises when we introduce behavior that is largely state-dependent or where state transitions must comply with specific business rules.
Take the following example (which uses an enumeration to represent state):
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
}
}
You can see that this class will soon become quite verbose as we add methods for Reject, Finish, Delete, etc.
Instead, we can introduce a state template that allows us to encapsulate each state as an object:
public abstract class VacancyState {
protected Vacancy vacancy;
public VacancyState(Vacancy vacancy) {
this.vacancy = vacancy;
}
public abstract void Approve();
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;
}
}
This simplifies the transition between states, performs logic based on the current state, or adds new states if we need:
vacancy.State.Approve();
model.ShowRejectButton = vacancy.State.CanReject();
, , . Greg Young , ( ApprovedVacancy, UnapprovedVacancy .. ), , .
?