一、概述
一般问题:同一个对象因内部环境变化而展现出完全不同的表示。
核心方案:允许一个对象在其内部状态改变时改变它的行为,看起来就像改变了它的类一样。
设计意图:事物是有状态的,有些类也需要考虑不同状态下表现出完全不同的行为。如果把状态变化直接封装在类内部方法中,我们需要写复杂的条件判断语句——根据不同状态,执行不同的代码逻辑。简单情况这样写没什么问题,如果类的状态变化复杂,条件判断逻辑过于臃肿,就需要考虑引入设计模式了。设计模式的一个原则就是提取变化,既然状态会变化,我们就提取不同状态下的具体实现封装成状态类,用一个状态类实例替换原来复杂的条件判断逻辑。
状态模式类图:
状态模式优化代码大致模型如下:
原代码:
int mState = 0; private void method () { //复杂的条件判断 if (mState == 1) { //do state1 } else if (mState == 2) { //do state2 } else if (mState == 3) { //do state3 } else { //do default } }
状态模式代码:
State mState; private void method(){ mState.method(); //直接执行state方法,替代复杂的条件判断 } //公开设置state接口 public void setState(State state){ mState = state; }
二、应用实战
在手机上经常能收到商品链接,点击链接后可以查看商品详细信息,这时如果点击购买或评价,就需要判断用户是否为登陆状态:登陆状态和非登陆状态是两种完全不同的处理结果。这里可以运用状态模式来解耦客户端和用户状态变化,其类图如下:
首先抽象出用户状态类:
public interface UserState { /** * 购买 */ public void buy(Context context); /** * 评论 */ public void comment(Context context); }
然后定义状态子类——登陆状态:
public class LogIn implements UserState{ @Override public void buy(Context context) { gotoPayActivity(); //进入支付界面 } @Override public void comment(Context context) { gotoCommentActivity(); //进入评论界面 } }
继续定义状态子类——非登陆状态:
public class LogOut implements UserState{ @Override public void buy(Context context) { gotoLogInActivity(); //进入登陆界面 } @Override public void comment(Context context) { gotoLogInActivity(); //进入登陆界面 } }
客户端Context类如下:
public class Context { //默认为非登录状态 UserState state = new LogOut(); //设置登陆状态 public void setState(UserState state){ this.state = state; } //购买 public void buy(Context context){ state.buy(context); } //评论 public void comment(Context context){ state.comment(context); } }
可以看出,Context不用写复杂的登陆判断语句,也不用关心用户具体登陆状态,实现了与用户状态解耦。
另外,如果现在要增加一个状态,比如账号冻结状态,只需要新增一个状态子类就行。
三、总结
总结:状态模式是一种行为型设计模式,主要解决的是当控制一个对象状态的条件表达式过于复杂时,把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。
用一句话概括状态模式:
万变不离其宗
优点:
- 简化了状态判断代码
- 将调用者与被调用者状态解耦
缺点:每一种状态对应一个子类,造成状态子类繁多