大家好,欢迎关注极客架构师,极客架构师,专注架构师成长,我是码农老吴。
本期是我刚刚推出的,《架构师基本功之设计模式》的第5期,我将结合京东订单的状态流转,来分享我们的第三个设计模式,状态模式(State pattern)。
结论先行
基于REIS分析模型,状态模式,包含三种角色,分别是状态服务方角色,状态客户方角色,特定状态服务方角色,状态模式的宗旨是通过特定状态服务方,将原来属于状态服务方的多状态模式,转化为单状态模式,降低了状态服务的逻辑复杂度,提高了状态服务的可扩展性。
基本思路
案例介绍:京东订单状态流转
第一版代码:普通方式
第二版代码:基于状态模式
状态模式定义,通用类图及代码
状态模式冗余代码化解大法:状态模式的二次方(下期内容)
状态模式 VS 策略模式(下期内容)
状态模式的真正舞台(下期内容)
案例介绍:京东订单状态流转模块

京东购物流程图
当我们在电商平台购物时,基本的流程是如下:
- 搜索自己需要的商品,
- 进入商品列表页面,点击商品,
- 进入商详页,了解商品之后,将商品加入购物车,
- 进入购物车页面,确认购买的商品之后,点击结算按钮,
- 进入结算页面,选择收货地址,配送方式等,点击支付按钮,
- 进入支付页面,进行订单支付,支付完成,
- 进入自己的订单列表页,可以看到自己刚刚下的订单。
当我们提交一个订单之后,订单在电商系统后台,会经历很多状态流转,直至到订单的完结状态(妥投,取消,删除等)。订单状态的流转,涉及电商系统的交易,仓储,配送等各个环节。当然,不同品类商品的订单,订单状态流转差异也是比较大的,像我以前所在的运营商部门,里面的手机充值业务,因为是虚拟商品,不涉及仓储,配送等环节,状态流转就简化了不少。
而实物类商品,如手机销售业务,状态流转就比较复杂,如果是合约机,或者以贷款的方式分期购买合约机,状态流转的复杂程度,可以用恐怖二字来形容,买个东西,比西天取经还难。一个订单,涉及了实物商品(手机),虚拟商品(合约),金融服务(分期贷款),业务流程复杂到让产品经理焦头烂额,让架构师眉头紧锁,开发这样一个项目,有直接关系的部门一个巴掌都数不过来,还不包括有间接关系的部门,系统上线前,我们常常想,只要有一个用户能下单成功,我们就算没有白忙活。

订单状态流转图
一般的实物商品订单,通常要经历交易,仓储,配送等几个环节,每个环节,订单有不同的状态。比如:
- 交易环节,就有未支付,已支付,已对账等几个状态。
- 仓储环节,有打印面单,出库,打包,发货等状态。
- 配送环节,有分拣,配送,妥投等状态。
对于订单的每个状态,前端支持的用户行为也各不相同,我们以几个常见的用户操作为例,方便我们进行后面的讲解。下面是常见的几个订单状态,支持的用户操作。
- 未支付:支付,取消,删除。
- 已支付:催单
- 已发货:催单
- 已妥投:点评,删除
- 已删除:用户就看不到了,所以不支持任何操作。
系统挑战
订单的状态流转,受商品和业务的影响,是复杂多变的,如何简化在订单特定状态下的业务处理逻辑,提高系统对状态的高扩展性,是我们要利用状态模式解决的问题。
功能限制,我们的重点是讲解状态模式,所以对实现的功能模块需要进行一定的简化。我们只关注以上五个订单状态(未支付,已支付,已发货,已妥投,已删除)和三个订单相关的操作(支付,催单,删除)。
下面我们看第一版代码,以普通的方式实现。
第一版代码,普通方式
UML类图

类图中关键接口和类如下:
IOrderService:订单服务接口
OrderServiceV1:订单服务实现类
OrderInfo:订单信息实体类
OrderStateEnum:订单状态枚举类
TestOrderServiceV1:测试类
IOrderService:订单服务接口
package com.geekarchitect.patterns.demo0201;
/**
* 订单服务接口
*
* @author 极客架构师@吴念
* @createTime 2022/4/15
*/
public interface IOrderService {
/**
* @author: 极客架构师@吴念
* @date: 2022/4/15
* @param: [orderID]
* @return: void
*/
boolean pay(OrderInfo orderInfo);
/**
* 催单
*
* @author: 极客架构师@吴念
* @date: 2022/4/15
* @param: [orderID, message]
* @return: void
*/
boolean reminder(OrderInfo orderInfo);
/**
* 删除订单
*
* @author: 极客架构师@吴念
* @date: 2022/4/15
* @param: [OrderID]
* @return: boolean
*/
boolean delete(OrderInfo orderInfo);
/**
* 修改订单状态
* @author: 极客架构师@吴念
* @date: 2022/4/18
* @param: [orderInfo]
* @return: void
*/
void changeState(OrderInfo orderInfo,OrderStateEnum newState);
}
OrderServiceV1:订单服务实现类
package com.geekarchitect.patterns.demo0201;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 订单服务
* @author 极客架构师@吴念
* @createTime 2022/4/15
*/
public class OrderServiceV1 implements IOrderService {
private static final Logger LOG = LoggerFactory.getLogger(OrderServiceV1.class);
@Override
public boolean pay(OrderInfo orderInfo) {
boolean result=false;
switch (orderInfo.getOrderStateEnum()){
case UNPAID:
//此处省略几百行业务代码
LOG.info("支付成功");
changeState(orderInfo,OrderStateEnum.PAID);
result=true;
break;
default:
LOG.info("无法支付");
}
return result;
}
@Override
public boolean reminder(OrderInfo orderInfo) {
boolean result=false;
switch (orderInfo.getOrderStateEnum()){
case PAID:
case DELIVERED:
//此处省略几百行业务代码
LOG.info("催单成功");
result=true;
break;
default:
LOG.info("无法催单");
}
return result;
}
@Override
public boolean delete(OrderInfo orderInfo) {
boolean result=false;
switch (orderInfo.getOrderStateEnum()){
case UNPAID:
case DONE:
//此处省略几百行业务代码
LOG.info("删除成功");
changeState(orderInfo,OrderStateEnum.DELETED);
result=true;
break;
default:
LOG.info("无法删除");
}
return result;
}
@Override
public void changeState(OrderInfo orderInfo,OrderStateEnum newState) {
LOG.info("订单状态:由{}转变为{}",orderInfo.getOrderStateEnum().getName(),newState.getName());
orderInfo.setOrderStateEnum(newState);
}
}
OrderInfo:订单信息实体类
package com.geekarchitect.patterns.demo0201;
import lombok.Data;
/**
* 订单信息
* @author 极客架构师@吴念
* @createTime 2022/4/16
*/
@Data
public class OrderInfo {
/**
* 订单id
*/
private String ID;
/**
* 订单状态
*/
private OrderStateEnum orderStateEnum;
}
OrderStateEnum:订单状态枚举类
package com.geekarchitect.patterns.demo0201;
public enum OrderStateEnum {
UNPAID("未支付", 0), PAID("已支付", 1), DELIVERED("已发货", 2), DONE("已妥投", 3), DELETED("已删除", 4);
private String name;
private int code;
OrderStateEnum(String name, int code) {
this.name = name;
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
TestOrderServiceV1:测试类
package com.geekarchitect.patterns.demo0201;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author 极客架构师@吴念
* @createTime 2022/4/18
*/
public class TestOrderServiceV1 {
private static final Logger LOG = LoggerFactory.getLogger(TestOrderServiceV1.class);
public static void main(String[] args) {
LOG.info("第一版代码:普通方式");
TestOrderServiceV1 testOrderService = new TestOrderServiceV1();
//testOrderService.testPay();
testOrderService.testReminder();
}
public void testPay() {
LOG.info("测试支付方法pay()");
OrderInfo orderInfo = new OrderInfo();
orderInfo.setID("1");
orderInfo.setOrderStateEnum(OrderStateEnum.UNPAID);
IOrderService orderService = new OrderServiceV1();
//第一次支付
orderService.pay(orderInfo);
//第二次支付
orderService.pay(orderInfo);
}
public void testReminder() {
LOG.info("测试催单方法reminder()");
OrderInfo orderInfo = new OrderInfo();
orderInfo.setID("1");
orderInfo.setOrderStateEnum(OrderStateEnum.UNPAID);
IOrderService orderService = new OrderServiceV1();
//第一次催单
orderService.reminder(orderInfo);
//支付
orderService.pay(orderInfo);
//第二次催单
orderService.reminder(orderInfo);
}
}
运行结果


头脑风暴

如上图代码所示,这版代码的特点是,OrderServiceV1(订单服务类)上面的每个方法,都需要考虑订单的各个状态,业务逻辑相对来说复杂一些,如果有一个状态需要调整,上面的方法基本都会受到影响,如果要增加一个新的订单状态,上面的方法都需要进行相应修改,扩展性不高,不符合开闭原则。下面我们基于状态模式进行重构。
第二版代码,基于状态模式
UML类图

新增接口和类
IOrderStateService:订单状态服务接口
AbstractOrderStateService:订单状态服务抽象类
UnpaidOrderStateService:未支付订单状态服务类
PaidOrderStateService:已支付订单状态服务类
DeliveredOrderStateService:已发货订单状态服务类
DeletedOrderStateService:已删除订单状态服务类
受影响的接口和类
OrderServiceV2:订单服务类
TestOrderServiceV2:测试类
UML序列图

IOrderStateService:订单状态服务接口
package com.geekarchitect.patterns.demo0202;import com.geekarchitect.patterns.demo0201.OrderInfo;/** * 订单状态服务接口 * * @author 极客架构师@吴念 * @createTime 2022/4/18 */public interface IOrderStateService { /** * 支付 * @author: 极客架构师@吴念 * @date: 2022/4/15 * @param: [orderID] * @return: void */ boolean pay(OrderInfo orderInfo); /** * 催单 * * @author: 极客架构师@吴念 * @date: 2022/4/15 * @param: [orderID, message] * @return: void */ boolean reminder(OrderInfo orderInfo); /** * 删除订单 * * @author: 极客架构师@吴念 * @date: 2022/4/15 * @param: [OrderID] * @return: boolean */ boolean delete(OrderInfo orderInfo);}
AbstractOrderStateService:订单状态服务抽象类
package com.geekarchitect.patterns.demo0202;import com.geekarchitect.patterns.demo0201.IOrderService;import com.geekarchitect.patterns.demo0201.OrderInfo;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 订单状态服务抽象类 * @author 极客架构师@吴念 * @createTime 2022/4/18 */public class AbstractOrderStateService implements IOrderStateService { private static final Logger LOG = LoggerFactory.getLogger(AbstractOrderStateService.class); protected IOrderService orderService; public AbstractOrderStateService(IOrderService orderService) { this.orderService = orderService; } @Override public boolean pay(OrderInfo orderInfo) { LOG.info("订单状态:{},无法支付", orderInfo.getOrderStateEnum().getName()); return false; } @Override public boolean reminder(OrderInfo orderInfo) { LOG.info("订单状态:{},无法催单", orderInfo.getOrderStateEnum().getName()); return false; } @Override public boolean delete(OrderInfo orderInfo) { LOG.info("订单状态:{},无法删除", orderInfo.getOrderStateEnum().getName()); return false; }}
UnpaidOrderStateService:未支付订单状态服务类
package com.geekarchitect.patterns.demo0202;import com.geekarchitect.patterns.demo0201.IOrderService;import com.geekarchitect.patterns.demo0201.OrderInfo;import com.geekarchitect.patterns.demo0201.OrderStateEnum;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 未支付订单状态服务 * * @author 极客架构师@吴念 * @createTime 2022/4/18 */public class UnpaidOrderStateService extends AbstractOrderStateService { private static final Logger LOG = LoggerFactory.getLogger(UnpaidOrderStateService.class); public UnpaidOrderStateService(IOrderService orderService) { super(orderService); } @Override public boolean pay(OrderInfo orderInfo) { //此处省略几百行业务代码 LOG.info("支付成功"); this.orderService.changeState(orderInfo, OrderStateEnum.PAID); return true; } @Override public boolean delete(OrderInfo orderInfo) { //此处省略几百行业务代码 LOG.info("删除成功"); this.orderService.changeState(orderInfo, OrderStateEnum.DELETED); return true; }}
PaidOrderStateService:已支付订单状态服务类
package com.geekarchitect.patterns.demo0202;import com.geekarchitect.patterns.demo0201.IOrderService;import com.geekarchitect.patterns.demo0201.OrderInfo;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 已支付订单状态服务 * * @author 极客架构师@吴念 * @createTime 2022/4/18 */public class PaidOrderStateService extends AbstractOrderStateService { private static final Logger LOG = LoggerFactory.getLogger(PaidOrderStateService.class); public PaidOrderStateService(IOrderService orderService) { super(orderService); } @Override public boolean reminder(OrderInfo orderInfo) { //此处省略几百行业务代码 LOG.info("催单成功"); return true; }}
DeliveredOrderStateService:已发货订单状态服务类
package com.geekarchitect.patterns.demo0202;import com.geekarchitect.patterns.demo0201.IOrderService;import com.geekarchitect.patterns.demo0201.OrderInfo;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 已发货订单状态服务 * * @author 极客架构师@吴念 * @createTime 2022/4/18 */public class DeliveredOrderStateService extends AbstractOrderStateService { private static final Logger LOG = LoggerFactory.getLogger(DeliveredOrderStateService.class); public DeliveredOrderStateService(IOrderService orderService) { super(orderService); } @Override public boolean reminder(OrderInfo orderInfo) { //此处省略几百行业务代码 LOG.info("催单成功"); return true; }}
DeletedOrderStateService:已删除订单状态服务类
package com.geekarchitect.patterns.demo0202;import com.geekarchitect.patterns.demo0201.IOrderService;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 已删除订单状态服务 * * @author 极客架构师@吴念 * @createTime 2022/4/18 */public class DeletedOrderStateService extends AbstractOrderStateService { private static final Logger LOG = LoggerFactory.getLogger(DeletedOrderStateService.class); public DeletedOrderStateService(IOrderService orderService) { super(orderService); }}
OrderServiceV2:订单服务类
package com.geekarchitect.patterns.demo0202;import com.geekarchitect.patterns.demo0201.IOrderService;import com.geekarchitect.patterns.demo0201.OrderInfo;import com.geekarchitect.patterns.demo0201.OrderStateEnum;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.HashMap;import java.util.Map;/** * 订单服务类 * @author 极客架构师@吴念 * @createTime 2022/4/15 */public class OrderServiceV2 implements IOrderService { private static final Logger LOG = LoggerFactory.getLogger(OrderServiceV2.class); private final Map<Integer, IOrderStateService> orderStateServiceMap = new HashMap<>(); public OrderServiceV2() { //初始化状态服务对象 orderStateServiceMap.put(OrderStateEnum.UNPAID.getCode(), new UnpaidOrderStateService(this)); orderStateServiceMap.put(OrderStateEnum.PAID.getCode(), new PaidOrderStateService(this)); orderStateServiceMap.put(OrderStateEnum.DELIVERED.getCode(), new DeliveredOrderStateService(this)); orderStateServiceMap.put(OrderStateEnum.DELETED.getCode(), new DeletedOrderStateService(this)); } private IOrderStateService getOrderStateService(OrderInfo orderInfo) { IOrderStateService orderStateService = orderStateServiceMap.get(orderInfo.getOrderStateEnum().getCode()); if (orderStateService != null) { return orderStateService; } throw new UnsupportedOperationException("该状态不支持"); } @Override public boolean pay(OrderInfo orderInfo) { IOrderStateService orderStateService = getOrderStateService(orderInfo); return orderStateService.pay(orderInfo); } @Override public boolean reminder(OrderInfo orderInfo) { IOrderStateService orderStateService = getOrderStateService(orderInfo); return orderStateService.reminder(orderInfo); } @Override public boolean delete(OrderInfo orderInfo) { IOrderStateService orderStateService = getOrderStateService(orderInfo); return orderStateService.delete(orderInfo); } @Override public void changeState(OrderInfo orderInfo, OrderStateEnum newState) { LOG.info("订单状态:由{}转变为{}", orderInfo.getOrderStateEnum().getName(), newState.getName()); orderInfo.setOrderStateEnum(newState); }}
TestOrderServiceV2:测试类
package com.geekarchitect.patterns.demo0202;import com.geekarchitect.patterns.demo0201.IOrderService;import com.geekarchitect.patterns.demo0201.OrderInfo;import com.geekarchitect.patterns.demo0201.OrderStateEnum;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 测试类 * * @author 极客架构师@吴念 * @createTime 2022/4/18 */public class TestOrderServiceV2 { private static final Logger LOG = LoggerFactory.getLogger(TestOrderServiceV2.class); public static void main(String[] args) { LOG.info("第二版代码:基于状态模式"); TestOrderServiceV2 testOrderService = new TestOrderServiceV2(); testOrderService.testPay(); //testOrderService.testReminder(); //testOrderService.testDelete(); } public void testPay() { LOG.info("测试支付方法pay()"); OrderInfo orderInfo = new OrderInfo(); orderInfo.setID("1"); orderInfo.setOrderStateEnum(OrderStateEnum.UNPAID); IOrderService orderService = new OrderServiceV2(); //第一次支付 orderService.pay(orderInfo); //第二次支付 orderService.pay(orderInfo); } public void testReminder() { LOG.info("测试催单方法reminder()"); OrderInfo orderInfo = new OrderInfo(); orderInfo.setID("1"); orderInfo.setOrderStateEnum(OrderStateEnum.UNPAID); IOrderService orderService = new OrderServiceV2(); //第一次催单 orderService.reminder(orderInfo); //支付 orderService.pay(orderInfo); //第二次催单 orderService.reminder(orderInfo); } public void testDelete() { LOG.info("测试删除订单方法delete()"); OrderInfo orderInfo = new OrderInfo(); orderInfo.setID("1"); orderInfo.setOrderStateEnum(OrderStateEnum.DONE); IOrderService orderService = new OrderServiceV2(); orderService.delete(orderInfo); }}
运行结果


头脑风暴
这一版,我们就实现了基于状态模式的代码。和上一版比较,我们增加了一个接口,一个抽象类和四个实现类
IOrderStateService:订单状态服务接口
AbstractOrderStateService:订单状态服务抽象类
UnpaidOrderStateService:未支付订单状态服务类
PaidOrderStateService:已支付订单状态服务类
DeliveredOrderStateService:已发货订单状态服务类
DeletedOrderStateService:已删除订单状态服务类
看到这里,大家可能都要气得吐血啦,吐血没关系,可以服用东阿阿胶,他们的广告语写的好,东方有佳人,透如琥珀润如玉,百年堂阿胶,程序员的美丽工程师 (这个有点像插入广告哦)。
代码量没有精简,反而暴增,仅仅状态服务类就增加了四个,分别对应四个状态,那要是有几十个状态,就需要增加几十个相应的类,对,是这样的,就是要这么干。
不仅仅是类的增加,方法的实现,代码重复的概率也大大提高了。虽然我们通过抽象父类,提供了各个方法的默认操作,对代码进行了简化,但是还是有可能重复的。因为有不少方法,是支持多个状态的。
能量守恒,物质不灭,不仅仅是物理学定理,也同样适用于我们软件开发行业。这里就是要用空间(更多的类),来换取每个方法的逻辑简化,换取状态的高扩展性。
至于如何简化这些代码,是另外一个问题,不是这个模式所关心的,在后面的状态模式冗余代码化解大法中,我们会聊聊这个问题,下面我们就看看什么是状态模式。
状态模式(State Pattern)定义
Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.
—— Gof《Design Patterns: Elements of Reusable Object-Oriented Software》
中文解释如下:
允许一个对象,在其内部状态改变时,改变它的行为。对象看起来似乎修改了它的类。
这个定义里面我们要注意的关键词只有一个,就是状态。
状态
谁的状态?
对象的状态
状态怎么了?
状态发生了变化,被谁改变的,如何改变的,定义里面都没有,说明这些都不重要,重要的是它改变了。
状态改变的后果?
状态改变的后果,就是对象的行为发生了变化,在面向对象的编程思想体系中,行为通常就是类中定义的方法,或者说对象的方法。以前的面向过程语言里面,被称为函数。
一般情况下,类中的方法,在定义类的时候功能已经明确了,固定了,但是这里它的行为却发生了变化,造成的后果就是,给人一种错觉,对象对应的类发生了变化,既然是错觉,就意味着类并没有发生变化。
那么类的方法,如何才能发生变化,是状态模式要解决的重要问题。
在我们上面的案例中。

当订单状态发生变化时,OrderServiceV2订单服务类在接收到客户请求时,会根据订单状态,调用不同的状态服务类的方法,这就是它实现改变对象行为的方式,就是这么简单,直接。
总结一下,状态模式,就是当对象的内部状态发生变化时,对象方法的行为,也会随之改变,给人造成一种对象对应的类发生了改变的错觉。
下面我们用我自创的设计模式分析模型REIS模式,对状态模式进行分析,大家就会理解的更透彻一些。
REIS模型分析状态模式
REIS模型是我总结的分析设计模式的一种方法论,主要包括场景(scene),角色(role),交互(interaction),效果(effect)四个要素,后面我会专门分享一下这套方法论。
场景(Scene)
场景,也就是我们在什么情况下,遇到了什么问题,需要使用某个设计模式。
对于状态模式,当出现以下情况时,可能需要使用状态模式。
- 从业务角度看:
当一个业务依赖的业务流程,受到对象的状态影响比较大,而且对象的状态需要有一定的扩展性时,可以考虑使用状态模式,比如常见的工作流系统。
- 从技术角度看:
当一个类的多个方法,都受到同一个对象状态值影响时,可以考虑使用状态模式。
当一个类的多个方法,里面都充满了分支语句(if,switch),这些分支语句的条件都是某个对象的状态时,可以考虑使用状态模式,这个比较直白一些。
角色(Role)
角色,一般为设计模式出现的类,或者对象。每种角色有自己的职责。
在状态模式中,包含三种角色,状态服务方角色,状态客户方角色,特定状态服务方角色。
状态服务方角色(state server role):状态服务方,就是定义里面所说的那个对象,它的职责如下。
- 提供状态:这个状态可以是我们常规上理解的对象的某个属性,这属于物理的,真实存在的状态。也可以是逻辑的,也就是这个状态可能不存在,只是我们头脑中想象的,或者即使存在,但并不在这个对象里面。我们的案例里面,订单状态,就不属于OrderServiceV2类(订单服务类)的属性中,但它确确实实的起到了状态的作用。
- 提供状态控方法:什么是状态控方法呢,说到手机控,电脑控,电视控,大家应该能很容易理解它的含义,而状态控方法,也是同样的思路,就是这些方法都受到某个状态的影响,控制,他们的行为,会根据状态而产生变化,所以叫状态控方法,这是我自创的,感觉比较形象些,在原著里面称为“state-specific”。
- 提供状态流转的方法:状态流转,也就是从一个状态,切换到另外一个状态,状态服务方,可以提供这个方法,也可以不提供,如果提供了,那么就需要保证后面的特定状态服务方角色,可以访问到这个方法。所以经常见到状态服务方,把自己(this)作为参数,传递给了特定状态服务方角色。我们的通用代码里面,也是这么实现的。
状态客户方角色(state client role):状态客户方,在这个模式中,基本上就是滥竽充数的,起到占位符的作用,定义它只是为了让语境更完整,类图,序列图更好看些,如果非要说它有什么职责的话,那就是调用状态服务方里面的状态控方法,而且调用的时候,它是不用考虑状态问题。
特定状态服务方角色(specific state server role):如果说状态服务方角色是整个状态模式的大哥,那么特定状态服务方角色,就是围绕在大哥身边的一群小弟。前者立志要给状态客户方,提供全状态的服务,口头语是“一切状态,我帮你搞定”,后者则是专注在特定状态的服务上,口头语是“这个状态的事情,我帮大哥搞定”。所有的特定状态服务方,像小弟一样,齐心协力,团结一致,帮助状态服务方,也就是他们的大哥,实现所有的服务,它的职责如下。
- 提供特定状态下,所有状态控方法的实现:也就是状态服务方角色里面的状态控方法,在这里都需要实现,只不过,因为状态明确,单一,所以代码实现逻辑难度会大大降低。
- 状态流转:特定状态服务方角色,里面实现的状态控方法,可以根据业务需求,进行状态切换。但是要注意,状态一旦切换,控制权就得交还给状态服务方(它们的大哥),由状态服务方,根据新的状态,指派相应的特定状态服务方对象,这一点要特别注意。
在上面的第二版代码,基于状态模式实现的订单服务中。
状态服务方角色对应的接口和类如下:
OrderServiceV2:订单服务类
特定状态服务方角色对应的接口和类如下:
IOrderStateService:订单状态服务接口
AbstractOrderStateService:订单状态服务抽象类
UnpaidOrderStateService:未支付订单状态服务类
PaidOrderStateService:已支付订单状态服务类
DeliveredOrderStateService:已发货订单状态服务类
DeletedOrderStateService:已删除订单状态服务类
交互(interaction)
交互,是指设计模式中,各种角色是如何交互的,一般用UML中的序列图,活动图来表示。简单的说就是角色之间是如何配合,完成设计模式的使命的。
状态模式,交互不是特别复杂,如下所示。

基本流程如下:
1,状态客户方角色,发起请求,调用状态服务方里面的状态控方法。
2,状态服务方,根据当前的状态,获取相应的特定状态服务方对象。
3,调用特定状态服务方对象相应的方法。
4,依次返回结果。
综上所述,基于REIS分析模型,状态模式,包含三种角色,分别是状态服务方角色,状态客户方角色,特定状态服务方角色,状态模式的宗旨是通过特定状态服务方,将原来属于状态服务方的多状态模式,转化为单状态模式,降低了状态服务的逻辑复杂度,提高了状态服务的可扩展性。
我们还是以找房子的案例,让大家理解状态模式。
假如你刚刚大学毕业,来北京找工作,人生地不熟,需要先找个地方住。来到一家中介公司。找房整个过程,大致可以分为筛选房源,看房,签合约,付费等几个环节,每个环节,都可以理解为找房事件的一个状态,中介公司都安排专门的人员与你对接,给你提供服务。在这个案例中,你就是状态客户方,中介公司就是状态服务方,每个环节,与你对接的人员就是特定状态服务方。
效果(effect)
效果,使用该设计模式之后,达到了什么效果,有何意义,当然,也可以说说它的缺点,或者风险。
从我们前面的案例可以看出,状态模式达到了以下效果。
积极效果
- 大大降低了每个状态控方法的实现难度:以前是一个方法里面,需要考虑对象的所有状态,现在只需要考虑特定的状态。
- 提供了状态的扩展性:当一个状态下的行为有变化时,只需要修改相应的特定状态服务方对应的类即可,如果增加了新的状态,只需要增加一个新的特定状态服务方即可。
消极效果
- 类的个数增加了:原来一个类干的事情,分散到多个类里面了,而且是有多少个状态,就需要相应的增加多少个类。
- 状态控方法代码重复的概率急剧增加:原来只需要写一个方法,现在,需要在每个状态下,都实现该方法。极大的增加了代码重复的概率。如何降低代码重复率,是这个模式需要面临的难题。
状态模式的难点:如何降低特定状态服务方的方法代码重复率
正如上面我们在效果中所说的,使用了状态模式,状态控方法的代码重复率会急剧上升,如何降低代码重复率,可以有以下几个思路,我先简单阐述一下。
1,充分利用特定状态服务方继承的抽象类:这个方法,我们在上面已经使用过了,我们在抽象类中,实现了状态控方法的默认实现(这个其实就是钩子方法),这种方法,基于继承实现代码复用,缺点是会造成抽象类体积急剧膨胀。
2,给状态控方法,建立专用的辅助类:我们可以针对某些复杂的,代码重复率比较高的受控方法,建立专门的辅助类,以组合的方式实现代码复用。
3,状态模式的二次方:假设把所有状态分为几个状态组,某个状态控方法在每个组的代码是重复的,这个时候,我们就可以给这个状态控方法,再使用一次状态模式,也就是状态模式嵌套状态模式,有点看热闹的不怕事大,一个就已经够复杂的了,再嵌入一个,是不是更复杂,实际恰恰相反。
上面这些方案都比较复杂,一两句话说不清楚,我后面专门用一期进行讲解。
状态模式的疑问点:谁可以进行状态切换?
我们现在已经知道,在状态模式中有三个角色,状态服务方角色,状态客户方角色,特定状态服务方角色,那么应该由哪个角色,进行状态的切换。
推荐方案,也是最佳方案,就是在状态服务方角色中,提供状态转换的方法。而特定状态服务方对象,根据自己的需要,调用这个方法就可以。这个方法的好处是,状态切换集中在一个类里面,比较容易管理,一般情况下,状态切换代码量不会太大,但是又比较重要,统一管理,反而容易维护。
也可以分散在各个特定状态服务对象的类里面,缺点是分散,不容易管理,但是如果状态切换比较复杂,分散反而是一种优势。
另外,状态客户方,也可以根据自己的需要,调用服务方提供的状态切换方法,进行状态切换,这也是一种选择,但是一般不提倡。
通用类图和代码
下面我们看看状态模式的通用UML类图和代码。大家注意,我讲的和普通设计模式书籍上的,是不太一样的,看仔细了。
UML类图

接口及类:
StateClientRole:状态客户方
IStateServerRole:状态服务方接口
StateServerRole:状态服务方实现类
ISpecificStateServerRole:特定状态服务方接口
AbstractSpecificStateServerRole:特定状态服务方抽象类
SpecificStateServerRole01:特定状态服务方类1
SpecificStateServerRole02:特定状态服务方类2
StateEnum:状态枚举类
TestStateClientRole:测试类
UML序列图

StateClientRole:状态客户方
package com.geekarchitect.patterns.demo0204;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 状态客户方 * @author 极客架构师@吴念 * @createTime 2022/4/18 */public class StateClientRole { private static final Logger LOG = LoggerFactory.getLogger(StateClientRole.class); public void clientMethod() { LOG.info("状态模式通用代码"); IStateServerRole stateServerRole = new StateServerRole(); stateServerRole.stateServer1(); }}
IStateServerRole:状态服务方接口
package com.geekarchitect.patterns.demo0204;/** * 状态服务方-接口 * * @author 极客架构师@吴念 * @createTime 2022/4/18 */public interface IStateServerRole { /** * 状态控方法1 */ void stateServer1(); /** * 状态控方法2 */ void stateServer2(); /** * 非状态控方法 */ void unStateServer(); /** * 改变状态 */ void changeState();}
StateServerRole:状态服务方实现类
package com.geekarchitect.patterns.demo0204;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.HashMap;import java.util.Map;/** * 状态服务方-实现类 * * @author 极客架构师@吴念 * @createTime 2022/4/18 */public class StateServerRole implements IStateServerRole { private static final Logger LOG = LoggerFactory.getLogger(StateServerRole.class); private final Map<Integer, ISpecificStateServerRole> specificStateServerRoleMap = new HashMap<>(); public StateServerRole() { LOG.info("状态服务方:StateServerRole,初始化特定状态服务方对象"); specificStateServerRoleMap.put(StateEnum.STATE01.ordinal(), new SpecificStateServerRole01(this)); specificStateServerRoleMap.put(StateEnum.STATE02.ordinal(), new SpecificStateServerRole02(this)); } private ISpecificStateServerRole getSpecificStateServer() { LOG.info("状态服务方:StateServerRole,根据状态获取相应的特定状态服务方对象"); //根据实际业务实现该代码 return specificStateServerRoleMap.get(StateEnum.STATE01.ordinal()); } @Override public void stateServer1() { LOG.info("状态服务方:StateServerRole,执行stateServer1"); ISpecificStateServerRole specificStateServerRole = getSpecificStateServer(); specificStateServerRole.stateServer1(); } @Override public void stateServer2() { LOG.info("状态服务方:StateServerRole,执行stateServer2"); ISpecificStateServerRole specificStateServerRole = getSpecificStateServer(); specificStateServerRole.stateServer2(); } @Override public void changeState() { LOG.info("状态服务方:StateServerRole,执行changeState"); } @Override public void unStateServer() { }}
ISpecificStateServerRole:特定状态服务方接口
package com.geekarchitect.patterns.demo0204;/** * 特定状态服务方-接口 * * @author 极客架构师@吴念 * @createTime 2022/4/18 */public interface ISpecificStateServerRole { /** * 状态控方法1 */ void stateServer1(); /** * 状态控方法2 */ void stateServer2();}
AbstractSpecificStateServerRole:特定状态服务方抽象类
package com.geekarchitect.patterns.demo0204;/** * 特定状态服务方-抽象类 * * @author 极客架构师@吴念 * @createTime 2022/4/20 */public abstract class AbstractSpecificStateServerRole implements ISpecificStateServerRole { protected IStateServerRole stateServerRole; public AbstractSpecificStateServerRole(IStateServerRole stateServerRole) { this.stateServerRole = stateServerRole; } @Override public void stateServer1() { //默认实现 } @Override public void stateServer2() { //默认实现 }}
SpecificStateServerRole01:特定状态服务方类1
package com.geekarchitect.patterns.demo0204;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 特定状态服务方实现类 * * @author 极客架构师@吴念 * @createTime 2022/4/18 */public class SpecificStateServerRole01 extends AbstractSpecificStateServerRole { private static final Logger LOG = LoggerFactory.getLogger(SpecificStateServerRole01.class); public SpecificStateServerRole01(IStateServerRole stateServerRole) { super(stateServerRole); } @Override public void stateServer1() { LOG.info("特定状态服务方:SpecificStateServerRole01,执行stateServer1方法"); }}
SpecificStateServerRole02:特定状态服务方类2
package com.geekarchitect.patterns.demo0204;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * @author 极客架构师@吴念 * @createTime 2022/4/18 */public class SpecificStateServerRole02 extends AbstractSpecificStateServerRole { private static final Logger LOG = LoggerFactory.getLogger(SpecificStateServerRole02.class); public SpecificStateServerRole02(IStateServerRole stateServerRole) { super(stateServerRole); } @Override public void stateServer1() { LOG.info("特定状态服务方:SpecificStateServerRole02,执行stateServer1方法"); } @Override public void stateServer2() { LOG.info("特定状态服务方:SpecificStateServerRole02,执行stateServer2方法"); }}
StateEnum:状态枚举类
package com.geekarchitect.patterns.demo0204;public enum StateEnum { STATE01, STATE02}
TestStateClientRole:测试类
package com.geekarchitect.patterns.demo0204;/** * 测试类 * * @author 极客架构师@吴念 * @createTime 2022/4/20 */public class TestStateClientRole { public static void main(String[] args) { TestStateClientRole testStateClientRole = new TestStateClientRole(); testStateClientRole.testStateServer01(); } public void testStateServer01() { StateClientRole stateClientRole = new StateClientRole(); stateClientRole.clientMethod(); }}
运行结果

至此,状态模式讲解完毕。
下期内容预告:
状态模式冗余代码化解大法:状态模式的二次方
状态模式 VS 策略模式
状态模式的真正舞台
预知后事如何,且听下回分解
本期我们就分享到这里,关注我,我将持续分享更多架构师的相关文章和视频,我们下期见。

如若转载,请注明出处:https://www.sumrw.com/17018.html