概念
状态模式就是允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
对象的具体行为是根据它的当前状态而具体实现,是多态的。
适用性:
1、 一个对象的行为取决于它的状态,并且必须在运行时刻根据状态改变它的行为。
2、一个操作中包含庞大的多分支条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常,有多个操作包含这一相同的条件结构。State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
总结就是适合对象有不同的状态,并且根据各种动作状态改变的问题。
问题
遇到的业务商机状态转移就很适合用到状态模式:一个商机从某种商机状态经过转移动作更新为新的商机状态。
一开始的时候没有考虑到耦合、维护性、拓展性,只是简单的实现了功能。
demo:
/**
* 商机状态,抽象父类
*/
public abstract class AbstractBusinessOpportunityState {
/**
* 获取商机状态类型
*/
public abstract BusinessOpportunityConstants.Status getStateType();
/**
* 抢聊
*/
public abstract void grabChat();
/**
* 续聊
*/
public abstract void continueChat();
}
/**
* 商机
*/
public class BusinessOpportunity {
/**
* 商机状态
*/
AbstractBusinessOpportunityState state = null;
public BusinessOpportunity() {
// 初始态为无关商机
state = new NoBusinessState();
}
}
private BusinessOpportunityStateDAO businessOpportunityStateDAO;
{
businessOpportunityStateDAO = SpringUtils.getBean(BusinessOpportunityStateDAO.class);
}
public BusinessOpportunity(String agentId, String userId) throws Exception {
BusinessOpportunityStateDocument doc = businessOpportunityStateDAO.getByAgentIdAndUserId(agentId, userId);
if (null != doc) {
state = genBusinessOpportunityStateByType(doc.getState());
} else {
// 初始态为无关商机
state = new NoBusinessState();
}
}
public void grabChat() {
state.grabChat();
}
/**
* 根据状态类型枚举生成对应状态实例
*
* @param stateType
* @return
*/
private AbstractBusinessOpportunityState genBusinessOpportunityStateByType(Integer stateType) throws Exception {
AbstractBusinessOpportunityState state;
switch (stateType) {
case BusinessOpportunityConstants.STATE_NOT_GRAB_CHAT:
state = SpringUtils.getBean(NotGrabChatState.class);
break;
default:
}
return state;
}
}
/**
* 抢聊商机(未抢聊)
*/
public class NotGrabChatState extends AbstractBusinessOpportunityState {
@Override
public BusinessOpportunityConstants.Status getStateType() {
return BusinessOpportunityConstants.Status.NOT_GRAB_CHAT;
}
/**
* 动作的具体实现
**/
@Override
public void grabChat() {
}
}
/**
* 实现
*/
@Service
public class VirtualPhoneCallLogService {
public void addCallLog(VirtualPhoneCallLogDocument callLogDocument) {
BusinessOpportunity businessOpportunity = new BusinessOpportunity(callLogDocument.getAgentId(), callLogDocument.getUserId());
businessOpportunity.grabChat();
}
}
优化一
缺点:
这个对于业务的实现其实很直观,但是从整体设计师存在一些问题的。一是开发过程中发现会有比如说更新状态到数据库还有创建流水这些都是重复的请求,所有的动作都是由转态的那个动作实现,造成代码的耦合。三是对于之后的理解、维护、拓展都很难。
改进方法:
考虑到解耦的问题,但是有不能破坏掉转态模式的特性,就想到在所有动作请求之前,先都调用一个“中转”的接口,把公共的操作都抽象到里面,再进行对该接口进行调用。利用java的泛型以及函数式编程特性,实现传入调用函数的方式。
BusinessOpportunity.java
/**
* 对所有动作进行一个更新状态的加工
* @param t
* @param function
* @param <T>
*/
public <T> Object doAction(T t, Function<T, ActionExecuteResult> function) {
// 当前状态所需要做的处理
ActionExecuteResult result = function.apply(t);
// 更新商机状态、生成商机状态流水
state.updateState(stateDocument, result);
return (null == result) ? null : result.getData();
}
public Function<SendMessageCToBInfo, ActionExecuteResult> grabChat(){
return (sendMessageCToBInfo) ->
state.grabChat(stateDocument, sendMessageCToBInfo.getMessageId(), sendMessageCToBInfo.getSendTime());
}
VirtualPhoneCallLogService .java
public void addCallLog(VirtualPhoneCallLogDocument callLogDocument) {
BusinessOpportunity businessOpportunity = new BusinessOpportunity(callLogDocument.getAgentId(), callLogDocument.getUserId());
businessOpportunity.doAction(null, businessOpportunity.grabChat());
}
优化二
上面的问题都解决了之后,还有那么一个小小的问题,那就是父类里面的所有方法都是抽象的,每个继承的子类都需要重写所有的方法,但其实一个转态可能就只需要实现部分方法就行了。
这个解决办法就很简单,只需要把父类的方法改成普通方法就行。
“不要过度设计”,“组合优于继承”。
总结
抽象、解耦、不要过度设计、组合优于继承。这都是一些经常看到的规则,但是好像仅仅是停留在了看到上面,这一次的问题到时深刻的运用了这些规则,还是要多思考吧,一些简单的东西,要贯彻用好也是没那么容易的。
因为第一次用状态模式,所以看了好些文章介绍,还是一知半解的,最后还是翻了《设计模式》才算是完整的了解到了,看原始的资料比加工后的有用的多。