老生常谈:状态模式
背景
做为一名使用高级语言的程序员,面向对象的设计一直都是体现程序员能力的重要标准之一,所以在我工作两年后也就是2008年我也开始了对于面向对象设计的学习,主要是拿GOF设计模式来练手,尽管实际项目中没有真正的使用过。常见的23种设计模式,我从2008一至到2009年将近一年的时间我基本上将这些模式都写了一篇Demo,但可能是当时的认识问题有少部分的模式我并没有写下笔记,比如这篇重点分享的状态模式。
案例
由于我工作的项目大多以后台业务为主,所以简单的审核流程非常常见。一说到审核流程,我们可以说市面上有好多工作流的产品可以选择,这当然是好的,但有些项目刚开始规模比较小,需求也比较简单,从技术成本以及效率上来讲一般都不会考虑在项目刚开始的时候就引用工作流引擎,基本上都是通过常规的数据状态扭转即可达到目的,说的直白点就是根据不同的数据状态来做不同的操作。拿我最近的一个产品申请功能来讲,它具备一个典型的状态扭转流程:
- 保存
- 提交
- 审核
- 拒绝
- 预发布
- 上架
- 下架
- 删除
操作人员在实现产品申请的状态变更时,一般写程序步骤是这样的:
- 获取申请的当前状态
- 判定当前的申请状态是否允许做审核操作,一般会出现类似如下的代码:
private void checkStatusForEdit(ProductRequest request) throws ProductServiceException {
if(request.getAuditStatus()==AuditStatus.Approved
&&(request.getPublishStatus()==PublishStatus.Initial
||request.getPublishStatus()==PublishStatus.Preview)){
return;
}
else{
throw new ProductServiceException();
}
}
- 执行操作
这样的代码会出现在每个审核动作当中,而且有些环节的状态判断可能会很复杂。按上面提到的8个状态,我们在做这8个动作时也就对应8个判断条件。
动机
GOF里面有一个模式比较合适来解决这类状态的判断,它就是状态模式。核心作用就是状态扭转过程中复杂的条件判断逻辑转移到对应的子类中,从而将复杂的条件判断逻辑简单化。
做法
UML图
类说明
- 状态上下文
RequestContextServiceImpl,主要是定义了产品申请审核流程所有的动作,同时持有当前状态类的一个实例,上下文所定义的所有动作的实际执行都委托给持有的当前状态类来完成。
public class RequestContextServiceImpl {
//持有当前状态类的实例
private AbstractRequestStateService requestStateService;
public AbstractRequestStateService getRequestStateService() {
return requestStateService;
}
public void setRequestStateService(AbstractRequestStateService requestStateService) {
this.requestStateService = requestStateService;
this.requestStateService.setRequestContextService(this);
}
//下面的8个方法,对应产品申请审核流程的8个动作,均委托给当前状态类来实际完成
public ProductInfo unkonw(ProductParamInfo productParamInfo) throws ProductServiceException{
return this.requestStateService.unkonw(productParamInfo);
}
public ProductInfo unProcess(ProductParamInfo productParamInfo) throws ProductServiceException{
return this.requestStateService.unProcess(productParamInfo);
}
public ProductInfo deleted(ProductParamInfo productParamInfo) throws ProductServiceException{
return this.requestStateService.deleted(productParamInfo);
}
public ProductInfo bssSave(ProductSimpleParamInfo productParamInfo) throws ProductServiceException {
return this.requestStateService.bssSave(productParamInfo);
}
public boolean approved(ProductAuditParamInfo productAuditParamInfo) throws ProductServiceException{
return this.requestStateService.approved(productAuditParamInfo);
}
public boolean rejected(ProductAuditParamInfo productAuditParamInfo) throws ProductServiceException{
return this.requestStateService.rejected(productAuditParamInfo);
}
public boolean onShelf(ProductPublishParamInfo productPublishParamInfo) throws ProductServiceException{
return this.requestStateService.onShelf(productPublishParamInfo);
}
public boolean offShelf(ProductPublishParamInfo productPublishParamInfo) throws ProductServiceException{
return this.requestStateService.offShelf(productPublishParamInfo);
}
public boolean preview(ProductPublishParamInfo productPublishParamInfo) throws ProductServiceException{
return this.requestStateService.preview(productPublishParamInfo);
}
}
- 状态抽象类
AbstractStateService,主要是定义了完成状态上下文所定义的动作,同时持有状态上下文。
public abstract class AbstractRequestStateService {
//这里持有状态上下文的实例,当状态可以扭转到其它状态时,委托给上下文来完成
protected RequestContextServiceImpl requestContextService;
public RequestContextServiceImpl getRequestContextService() {
return requestContextService;
}
public void setRequestContextService(RequestContextServiceImpl requestContextService) {
this.requestContextService = requestContextService;
}
//这里状态具体类需要重写的方法
public abstract ProductInfo unkonw(ProductParamInfo productParamInfo) throws ProductServiceException;
public abstract ProductInfo unProcess(ProductParamInfo productParamInfo) throws ProductServiceException;
public abstract ProductInfo deleted(ProductParamInfo productParamInfo) throws ProductServiceException;
public abstract boolean approved(ProductAuditParamInfo productAuditParamInfo) throws ProductServiceException;
public abstract boolean rejected(ProductAuditParamInfo productPublishParamInfo) throws ProductServiceException;
public abstract boolean onShelf(ProductPublishParamInfo productPublishParamInfo) throws ProductServiceException;
public abstract boolean offShelf(ProductPublishParamInfo productPublishParamInfo) throws ProductServiceException;
public abstract boolean preview(ProductPublishParamInfo productPublishParamInfo) throws ProductServiceException;
}
- 具体的状态子类
实现状态抽象类,自已只负责完成当前状态的逻辑,非常清晰的定义了当前状态下都可以扭转到哪些状态。比如用户做审核通过操作,此时对应一个RequestApprovedStateService类,具体只实现approved方法,从流程上看,审核通过的申请可以做预发布,上下架三个操作,对应方法中的priview,onShelf,offShelf,这三个方法中的实现均是委托给状态上下文。审核通过的申请是不能被删除,也不能再次修改,所以在实现这三个方法时,直接抛出异常。
public class RequestApprovedStateServiceImpl extends AbstractRequestStateService {
@Autowired
private RequestStateContainerServiceImpl containerService;
@Override
public ProductInfo unkonw(ProductParamInfo productParamInfo) throws ProductServiceException {
throw this.unSupportIsvSaveException();
}
@Override
public ProductInfo unProcess(ProductParamInfo productParamInfo) throws ProductServiceException {
throw this.unSupportIsvSaveException();
}
@Override
public ProductInfo deleted(ProductParamInfo productParamInfo) throws ProductServiceException {
throw this.unSupportIsvDeletedException();
}
@Override
public boolean approved(ProductAuditParamInfo productAuditParamInfo) throws ProductServiceException {
//这里具体处理审核通过的逻辑
}
@Override
public boolean rejected(ProductAuditParamInfo productAuditParamInfo) throws ProductServiceException {
throw this.unSupportAuditException();
}
@Override
public boolean onShelf(ProductPublishParamInfo productPublishParamInfo) throws ProductServiceException {
this.getRequestContextService().setRequestStateService(this.containerService.getOnShelfStateService());
return this.getRequestContextService().onShelf(productPublishParamInfo);
}
@Override
public boolean offShelf(ProductPublishParamInfo productPublishParamInfo) throws ProductServiceException {
throw this.unSupportOffShelfException();
}
@Override
public boolean preview(ProductPublishParamInfo productPublishParamInfo) throws ProductServiceException {
this.getRequestContextService().setRequestStateService(this.containerService.getPreviewStateService());
return this.getRequestContextService().preview(productPublishParamInfo);
}
}
- 客户端调用
方面中不再有类似checkStatus的逻辑,只需要产品申请的状态转换成具体的状态类然后调用对应的方法即可,状态的判断交给具体的子类完成。
public boolean audit(ProductAuditParamInfo productAuditParamInfo) throws ProductServiceException{
ProductRequest productRequest=this.get(productAuditParamInfo.getId());
RequestContextServiceImpl requestContextService=new RequestContextServiceImpl();
//写一个工厂类,用来将申请的状态值转换成具体的状态子类
AbstractRequestStateService stateService=this.containerService.convert2StateServiceForUpdate(productRequest);
//这里设置状态上下文的当前状态实例
requestContextService.setRequestStateService(stateService);
//根据操作类型(通过或者拒绝)来调用具体的动作
if(productAuditParamInfo.getType().equals(ProductAuditType.Approval.getCode())){
return requestContextService.approved(productAuditParamInfo);
}
else {
return requestContextService.rejected(productAuditParamInfo);
}
}
总结
优点
- 将复杂的状态逻辑判断转移到具体的状态子类中,简化了客户端调用的逻辑
- 当某个状态的逻辑发生变更时,只需要解决子类实现。也利于扩展,比如增加状态,我们只需要增加子类,增加上下文的动作。
- 比较有利于单元测试,相比一堆的checkStatus
副作用
子类增多,导致程序的复杂性增强。其实也算不上副作用,也不是状态模式的问题,大多数设计模式都会引起类增多的情况,这主要是单一职责,封闭修改,扩展开放这些OO原则的必然结果。