浅谈设计模式 - 状态模式(十三)
前言
状态模式其实也是一个非常常见的模式,最常见的利用场景是线程的状态切换,最常应用的形式就是对于If/else进行解耦,另外这个模式能够配合 责任链模式组合搭配出各种不同的状态切换成果,能够用设计模式模仿一个简略的“工作流”。
优缺点:
状态模式非常明显的是用于解耦大量If/else的模式,所以他的长处非常突出,就是能够简化大量的if/else判断,然而毛病页非常显著,那就是程序的执行受限于状态,如果状态的常量十分多的状况下,仍然会呈现大量的if/else的景象,状态模式和策略模式一样一旦状况十分复杂的时候很容易造成类的收缩,当然少数状况下这种毛病简直能够疏忽,总比太多。
对于状态模式在jdk1.8之后的lambada表达式中能够有体现,lambada实现了java的参数“办法化”,这样极大地简化了类的收缩,然而可能比拟难以了解,并且一旦简单仍然倡议应用状态来实现切换,这样更不便保护。
状态模式的结构图:
上面是状态模式的结构图,自身比较简单,仿佛并没有什么特地的中央,然而当咱们和“策略模式”比照之后,仿佛很容易混同,上面咱们来看下这两个结构图的比照:
状态模式结构图:
策略模式结构图:
通过比照能够看到,状态和策略这两个模式是十分相似的,那么咱们应该如何辨别这两个模式呢?在平时的开发工作中,如果一个对象有很多种状态,并且这个对象在不同状态下的行为也不一样,那么咱们就能够应用状态模式来解决这个问题,然而如果你让同一事物在不同的时刻有不同的行为,能够应用策略模式触发不同的行为。打个比方,如果你想让开关呈现不同的行为,你须要设计两个状态开关,而后在事件处理的时候将逻辑散发到不同的状态实现触发,而如果你想要实现相似商品的折扣或者优惠促销,满减等等“模式”的时候更加适宜应用策略,当然如果无奈辨别也没有关系,哪个应用更为纯熟即可。
案例:糖果机
这是《head first设计模式》中对于状态模式案例当中的糖果机,咱们能够从上面的图中看到如果应用单纯的代码来实现上面的工作,就会呈现十分多难堪的状况,比方大量繁冗的if/else代码充斥,上面咱们来看下对于这个糖果机依照一般的形式要如何设计?
不应用设计模式
不应用设计模式的状况下,咱们通常的做法是定义状态常量,比方设置枚举或者间接设置final的标示位等等,咱们
首先咱们须要划分对象,糖果机和糖果,糖果机蕴含硬币的总钱数,糖果的数量等等。
- 定义四个状态:售罄,售出中,存在硬币,没有硬币
为了实现状态的实现,咱们须要设计相似枚举的常量来示意糖果机的状态。
- 状态设置为常量,而糖果机须要内置机器的状态
- 最初用逻辑代码和判断让糖果机外部进行工作,当然这会呈现巨多的if/else判断。
最初咱们的代码表现形式如下,用传统的模式咱们很可能写出相似的代码:
MechanicaState:定义了糖果机的状态,当然能够作为糖果机的公有外部类定义应用,也能够设计为枚举,这里偷懒设计为一个常量类的模式。
/** * 机器状态 */public final class MechanicaState { /** * 售罄 */ public static final int SOLD_OUT = 0; /** * 存在硬币 */ public static final int HAS = 1; /** * 没有硬币 */ public static final int NOT = 2; /** * 售出糖果中 */ public static final int SOLD = 4;}
CandyMechaica:糖果器,蕴含了糖果外部的工作办法,能够看到有十分多冗余的If/else判断:
/** * 糖果机 */public class CandyMechanica { /** * 默认是售罄的状态 */ private int sold_state = SOLD_OUT; /** * 糖果数量 */ private int count = 0; public CandyMechanica(int count) throws InterruptedException { if(count <= 0){ throw new InterruptedException("初始化失败"); }else { sold_state = NOT; } System.out.println("欢迎光临糖果机,以后糖果机的糖果数量为:"+ count); this.count = count; } /** * 启动糖果机 */ public void startUp(){ switch (sold_state){ case NOT: System.out.println("以后糖果机没有硬币,请先投入硬币"); break; case SOLD: System.out.println("糖果售出中,请稍后"); break; case HAS: sold_state = SOLD; candySold(); break; case SOLD_OUT: System.out.println("糖果已售罄"); break; } } /** * 投入硬币的操作 */ public void putCoin(){ switch (sold_state){ case NOT: sold_state = HAS; System.out.println("投入硬币胜利,请开启糖果机"); break; case SOLD: System.out.println("糖果售出中,请勿反复投放"); break; case HAS: System.out.println("以后曾经存在硬币,请勿反复投放"); break; case SOLD_OUT: System.out.println("糖果已售罄,您投入的硬币将会在稍后退回"); break; } } /** * 售出糖果 */ public void candySold(){ switch (sold_state){ case NOT: System.out.println("以后机器内没有硬币,请先投入硬币"); break; case SOLD: System.out.println("糖果已售出,请取走您的糖果"); count--; if(count == 0){ System.out.println("以后糖果曾经售罄"); sold_state = SOLD_OUT; } sold_state = NOT; break; case HAS: sold_state = NOT; System.out.println("以后曾经存在硬币,请勿反复投放"); break; } }}
最初就是对于下面的糖果机器进行简略的单元测试:
/** * 单元测试 */public class Main { public static void main(String[] args) throws InterruptedException { CandyMechanica candyMechanica = new CandyMechanica(5); candyMechanica.putCoin(); candyMechanica.startUp(); candyMechanica.startUp(); candyMechanica.putCoin(); candyMechanica.putCoin(); candyMechanica.startUp(); candyMechanica.putCoin(); candyMechanica.startUp(); candyMechanica.putCoin(); candyMechanica.startUp(); candyMechanica.putCoin(); candyMechanica.startUp(); }}/*运行后果:欢迎光临糖果机,以后糖果机的糖果数量为:5投入硬币胜利,请开启糖果机糖果已售出,请取走您的糖果以后糖果机没有硬币,请先投入硬币投入硬币胜利,请开启糖果机以后曾经存在硬币,请勿反复投放糖果已售出,请取走您的糖果投入硬币胜利,请开启糖果机糖果已售出,请取走您的糖果投入硬币胜利,请开启糖果机糖果已售出,请取走您的糖果投入硬币胜利,请开启糖果机糖果已售出,请取走您的糖果以后糖果曾经售罄Process finished with exit code 0*/
应用状态模式重构
接着咱们应用状态模式来重构一下下面的代码,咱们重点关注CandyMechanica
这个类,他的三个办法耦合了大量的if/else判断,在编写这种代码的时候不仅会使得代码非常的死板,而且很容易出错,我想没有人会喜爱写下面这样的代码,所以上面咱们应用状态模式看下如何重构:
糖果机分为四个状态,然而他们有着相似的行为:推入硬币,启动机器,推出糖果这三个办法
- 应用接口的模式定时状态的专用行为
- 咱们把下面三个状态抽取为状态的专用办法,然而context在哪里?
- context在这里的表现形式为糖果机,咱们应用在状态外部组合糖果机的模式实现糖果机的状态“切换”。
- 留神:因为应用了外部类的内置模式,所以有时候很多判断能够简化,更多的时候倡议抽出来作为独自的类。
最初他的表现形式如下:
CandyState:糖果机分为四个状态,然而他们有着相似的行为:推入硬币,启动机器,推出糖果这三个办法
/** * 糖果状态 */public interface CandyState { /** * 启动糖果机 */ void startUp(); /** * 投入硬币 */ void putCoin(); /** * 推出糖果 */ void candySold();}
糖果机还是很简单,当然并不像是下面的模式:
CandyMechanica:重写之后的糖果机,此糖果机把所有的状态解耦并且抽取为对象的模式。
/** * 状态模式重写糖果机 */public class CandyMechanica implements CandyState { private int count; /** * 以后状态 */ private CandyState nowState; // 有硬币 private CandyState hasState; // 无硬币 private CandyState notState; // 售罄 private CandyState solidOutState; // 售出中 private CandyState solidState; public CandyMechanica(int count) throws InterruptedException { notState = new NotState(this); solidOutState = new SoldOutState(this); hasState = new HasState(this); solidState = new SoldOutState(this); if (count <= 0) { throw new InterruptedException("初始化失败"); } else { nowState = notState; } this.count = count; } @Override public void startUp() { nowState.startUp(); } @Override public void putCoin() { nowState.putCoin(); } @Override public void candySold() { nowState.candySold(); } /** * */ public static class HasState implements CandyState { private CandyMechanica candyMechanica; public HasState(CandyMechanica candyMechanica) { this.candyMechanica = candyMechanica; } @Override public void startUp() { candyMechanica.nowState = candyMechanica.solidState; candyMechanica.candySold(); System.out.println("糖果售出中,请稍后"); } @Override public void putCoin() { System.out.println("以后已有糖果,请勿反复投入"); } @Override public void candySold() { System.out.println("糖果已售罄"); } } /** * 售罄状态 */ public static class SoldOutState implements CandyState { private CandyMechanica candyMechanica; public SoldOutState(CandyMechanica candyMechanica) { this.candyMechanica = candyMechanica; } @Override public void startUp() { System.out.println("糖果已售罄"); } @Override public void putCoin() { System.out.println("糖果已售罄,您投入的硬币将会在稍后退回"); } @Override public void candySold() { System.out.println("糖果已售罄"); } } /** * 售出状态 */ public static class SoldState implements CandyState { private CandyMechanica candyMechanica; public SoldState(CandyMechanica candyMechanica) { this.candyMechanica = candyMechanica; } @Override public void startUp() { System.out.println("糖果售出中,请稍后"); } @Override public void putCoin() { System.out.println("糖果售出中,请勿反复投放"); } @Override public void candySold() { System.out.println("糖果已售出,请取走您的糖果"); candyMechanica.count--; if (candyMechanica.count == 0) { System.out.println("以后糖果曾经售罄"); candyMechanica.nowState = candyMechanica.solidOutState; } candyMechanica.nowState = candyMechanica.notState; } } /** * 无硬币状态 */ public static class NotState implements CandyState { private CandyMechanica candyMechanica; public NotState(CandyMechanica candyMechanica) { this.candyMechanica = candyMechanica; } @Override public void startUp() { System.out.println("以后糖果机没有硬币,请先投入硬币"); } @Override public void putCoin() { candyMechanica.nowState = candyMechanica.hasState; System.out.println("投入硬币胜利,请开启糖果机"); } @Override public void candySold() { System.out.println("以后机器内没有硬币,请先投入硬币"); } }}
最初是单元测试的局部:
/** * 单元测试 */public class Main { public static void main(String[] args) throws InterruptedException { CandyMechanica candyMechanica = new CandyMechanica(5); candyMechanica.putCoin(); candyMechanica.putCoin(); candyMechanica.startUp(); candyMechanica.candySold(); candyMechanica.startUp(); candyMechanica.putCoin(); candyMechanica.startUp(); candyMechanica.putCoin(); candyMechanica.startUp(); candyMechanica.putCoin(); candyMechanica.startUp(); candyMechanica.putCoin(); candyMechanica.startUp(); }}/*运行后果:欢迎光临糖果机,以后糖果机的糖果数量为:5投入硬币胜利,请开启糖果机以后曾经存在硬币,请勿反复投放糖果已售出,请取走您的糖果以后机器内没有硬币,请先投入硬币以后糖果机没有硬币,请先投入硬币投入硬币胜利,请开启糖果机糖果已售出,请取走您的糖果投入硬币胜利,请开启糖果机糖果已售出,请取走您的糖果投入硬币胜利,请开启糖果机糖果已售出,请取走您的糖果投入硬币胜利,请开启糖果机糖果已售出,请取走您的糖果以后糖果曾经售罄*/
总结
本文的代码比拟多,状态模式也是和策略一样,只有看一眼样例代码即可。
上面咱们来具体总结一下状态模式的特点,应用状态模式的劣势有以下几个方面:
- 将利用的代码解耦,利于浏览和保护。咱们能够看到,在第一种计划中,咱们应用了大量的
if/else
来进行逻辑的判断,将各种状态和逻辑放在一起进行解决。在咱们利用相干对象的状态比拟少的状况下可能不会有太大的问题,然而一旦对象的状态变得多了起来,这种耦合比拟深的代码保护起来几乎就是噩梦。 - 将变动封装进具体的状态对象中,相当于将变动部分化,并且进行了封装。利于当前的保护与拓展。应用状态模式之后,咱们把相干的操作都封装进对应的状态中,如果想批改或者增加新的状态,也是很不便的。对代码的批改也比拟少,扩展性比拟好。
- 通过组合和委托,让对象在运行的时候能够通过扭转状态来扭转本人的行为。咱们只须要将对象的状态图画进去,专一于对象的状态扭转,以及每个状态有哪些行为。这让咱们的开发变得简略一些,也不容易出错,可能保障咱们写进去的代码品质是不错的。
写在最初
状态模式应用频率和策略模式差不多,应用的中央还是比拟多的,也是能够疾速的简化代码的一种设计模式。