Chinaunix首页 | 论坛 | 博客
  • 博客访问: 276544
  • 博文数量: 83
  • 博客积分: 2393
  • 博客等级: 大尉
  • 技术积分: 640
  • 用 户 组: 普通用户
  • 注册时间: 2006-04-24 15:14
文章分类

全部博文(83)

文章存档

2019年(21)

2011年(5)

2010年(2)

2009年(1)

2008年(9)

2007年(33)

2006年(12)

我的朋友

分类: Java

2019-09-17 22:14:17

因事暂停一周没继续复习设计模式,今天继续复习;



  状态模式
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
介绍意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
何时使用:代码中包含大量与对象状态有关的条件语句。
如何解决:将各种具体的状态类抽象出来。
关键代码:通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if...else 等条件选择语句。
应用实例: 1、打篮球的时候运动员可以有正常状态、不正常状态和超常状态。 2、曾侯乙编钟中,'钟是抽象接口','钟A'等是具体状态,'曾侯乙编钟'是具体环境(Context)。
优点: 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点: 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。
注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。

实现
我们将创建一个 State 接口和实现了 State 接口的实体状态类。Context 是一个带有某个状态的类。
StatePatternDemo,我们的演示类使用 Context 和状态对象来演示 Context 在状态改变时的行为变化。





状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类
状态模式策略模式很相似,也是将类的"状态"封装了起来,在执行动作时进行自动的转换,从而实现,类在不同状态下的同一动作显示出不同结果。它与策略模式的区别在于,这种转换是"自动","无意识"的。
状态模式的类图如下



状态模式的类图与策略模式一模一样,区别在于它们的意图。策略模式会控制对象使用什么策略,而状态模式会自动改变状态。看完下面的案例应该就清楚了。
现在有一个糖果机的需求摆在你面前,需要用Java实现。
我们分析一下,糖果机的功能可以分为下图所示的四个动作和四个状态:

在不同状态下,同样的动作结果不一样。例如,在"投了25分钱"的状态下"转动曲柄",会售出糖果;而在"没有25分钱"的状态下"转动曲柄"会提示请先投币。
简单思考后,我们写出如下的糖果机实现代码

点击(此处)折叠或打开

  1. public class NoPatternGumballMachine{
  2.     /*
  3.      * 四个状态
  4.      */
  5.     /**没有硬币状态*/
  6.     private final static int NO_QUARTER = 0;
  7.     /**投币状态*/
  8.     private final static int HAS_QUARTER = 1;
  9.     /**出售糖果状态*/
  10.     private final static int SOLD = 2;
  11.     /**糖果售尽状态*/
  12.     private final static int SOLD_OUT = 3;

  13.     private int state = SOLD_OUT;
  14.     private int candyCount = 0;

  15.     public NoPatternGumballMachine(int count) {
  16.         this.candyCount = count;
  17.         if(candyCount > 0)
  18.             state = NO_QUARTER;
  19.     }

  20.     /*
  21.      * 四个动作
  22.      */

  23.     /**
  24.      * 投币
  25.      */
  26.     public void insertQuarter() {
  27.         if(NO_QUARTER == state){
  28.             System.out.println("投币");
  29.             state = HAS_QUARTER;
  30.         }
  31.         else if(HAS_QUARTER == state){
  32.             System.out.println("请不要重复投币!");
  33.             returnQuarter();
  34.         }
  35.         else if(SOLD == state){
  36.             System.out.println("已投币,请等待糖果");
  37.             returnQuarter();
  38.         }else if(SOLD_OUT == state){
  39.             System.out.println("糖果已经售尽");
  40.             returnQuarter();
  41.         }
  42.     }

  43.     /**
  44.      * 退币
  45.      */
  46.     public void ejectQuarter() {
  47.         if(NO_QUARTER == state){
  48.             System.out.println("没有硬币,无法弹出");
  49.         }
  50.         else if(HAS_QUARTER == state){
  51.             returnQuarter();
  52.             state = NO_QUARTER;
  53.         }
  54.         else if(SOLD == state){
  55.             System.out.println("无法退币,正在发放糖果,请等待");
  56.         }else if(SOLD_OUT == state){
  57.             System.out.println("没有投币,无法退币");
  58.         }
  59.     }

  60.     /**
  61.      * 转动出糖曲轴
  62.      */
  63.     public void turnCrank() {
  64.         if(NO_QUARTER == state){
  65.             System.out.println("请先投币");
  66.         }
  67.         else if(HAS_QUARTER == state){
  68.             System.out.println("转动曲轴,准备发糖");
  69.             state = SOLD;
  70.         }
  71.         else if(SOLD == state){
  72.             System.out.println("已按过曲轴,请等待");
  73.         }else if(SOLD_OUT == state){
  74.             System.out.println("糖果已经售尽");
  75.         }
  76.     }

  77.     /**
  78.      * 发糖
  79.      */
  80.     public void dispense() {
  81.         if(NO_QUARTER == state){
  82.             System.out.println("没有投币,无法发放糖果");
  83.         }
  84.         else if(HAS_QUARTER == state){
  85.             System.out.println("this method don't support");
  86.         }
  87.         else if(SOLD == state){
  88.             if(candyCount > 0){
  89.                 System.out.println("分发一颗糖果");
  90.                 candyCount --;
  91.                 state = NO_QUARTER;
  92.             }
  93.             else{
  94.                 System.out.println("抱歉,糖果已售尽");
  95.                 state = SOLD_OUT;
  96.             }
  97.         }else if(SOLD_OUT == state){
  98.             System.out.println("抱歉,糖果已售尽");
  99.         }
  100.     }

  101.     /**
  102.      * 退还硬币
  103.      */
  104.     protected void returnQuarter() {
  105.         System.out.println("退币……");
  106.     }

  107. }

从代码里面可以看出,糖果机根据此刻不同的状态,而使对应的动作呈现不同的结果。这份代码已经可以满足我们的基本需求,但稍微思考一下,你会觉得这种实现代码似乎,功能太复杂了,扩展性很差,没有面向对象的风格。


假设由于新需求,要增加一种状态,那每个动作方法我们都需要修改,都要重新增加一条else语句。而如果需求变更,某个状态下的动作需要修改,我们也要同时改动四个方法。这样的工作将是繁琐而头大的。
怎么办? 六大设计原则之一
找出应用中可能需要变化之处,把他们独立出来。
在糖果机中,状态就是一直在变化的部分,不同的状态动作不一样。我们完全可以将其抽离出来


新的设计想法如下:
首先,我们定义一个State接口,在这个接口内,糖果机的每个动作都有一个对应的方法
然后为机器的每个状态实现状态类,这些类将负责在对应的状态下进行机器的行为
最后,我们要摆脱旧的条件代码,取而代之的方式是,将动作委托到状态类
定义一个State接口

点击(此处)折叠或打开

  1. public abstract class State {
  2.     /**
  3.      * 投币
  4.      */
  5.     public abstract void insertQuarter();

  6.     /**
  7.      * 退币
  8.      */
  9.     public abstract void ejectQuarter();

  10.     /**
  11.      * 转动出糖曲轴
  12.      */
  13.     public abstract void turnCrank();

  14.     /**
  15.      * 发糖
  16.      */
  17.     public abstract void dispense();

  18.     /**
  19.      * 退还硬币
  20.      */
  21.     protected void returnQuarter() {
  22.         System.out.println("退币……");
  23.     }
  24. }
为机器的每个状态实现状态类

点击(此处)折叠或打开

  1. /**
  2.  * 没有硬币的状态
  3.  */
  4. public class NoQuarterState extends State{
  5.     GumballMachine gumballMachine;

  6.     public NoQuarterState(GumballMachine gumballMachine) {
  7.         this.gumballMachine = gumballMachine;
  8.     }
  9.     @Override
  10.     public void insertQuarter() {
  11.         System.out.println("你投入了一个硬币");
  12.         //转换为有硬币状态
  13.         gumballMachine.setState(gumballMachine.hasQuarterState);
  14.     }

  15.     @Override
  16.     public void ejectQuarter() {
  17.         System.out.println("没有硬币,无法弹出");
  18.     }

  19.     @Override
  20.     public void turnCrank() {
  21.         System.out.println("请先投币");
  22.     }

  23.     @Override
  24.     public void dispense() {
  25.         System.out.println("没有投币,无法发放糖果");
  26.     }

  27. }

  28. /**
  29.  * 投硬币的状态
  30.  */
  31. public class HasQuarterState extends State{
  32.     GumballMachine gumballMachine;

  33.     public HasQuarterState(GumballMachine gumballMachine) {
  34.         this.gumballMachine = gumballMachine;
  35.     }
  36.     @Override
  37.     public void insertQuarter() {
  38.         System.out.println("请不要重复投币!");
  39.         returnQuarter();
  40.     }

  41.     @Override
  42.     public void ejectQuarter() {
  43.         returnQuarter();
  44.         gumballMachine.setState(gumballMachine.noQuarterState);
  45.     }

  46.     @Override
  47.     public void turnCrank() {
  48.         System.out.println("转动曲轴,准备发糖");
  49.         gumballMachine.setState(gumballMachine.soldState);
  50.     }

  51.     @Override
  52.     public void dispense() {
  53.         System.out.println("this method don't support");
  54.     }

  55. }

  56. /**
  57.  * 出售的状态
  58.  */
  59. public class SoldState extends State{
  60.     GumballMachine gumballMachine;

  61.     public SoldState(GumballMachine gumballMachine) {
  62.         this.gumballMachine = gumballMachine;
  63.     }

  64.     @Override
  65.     public void insertQuarter() {
  66.         System.out.println("已投币,请等待糖果");
  67.         returnQuarter();
  68.     }

  69.     @Override
  70.     public void ejectQuarter() {
  71.         System.out.println("无法退币,正在发放糖果,请等待");
  72.     }

  73.     @Override
  74.     public void turnCrank() {
  75.         System.out.println("已按过曲轴,请等待");
  76.     }

  77.     @Override
  78.     public void dispense() {
  79.         int candyCount = gumballMachine.getCandyCount();
  80.         if(candyCount > 0){
  81.             System.out.println("分发一颗糖果");
  82.             candyCount--;
  83.             gumballMachine.setCandyCount(candyCount);
  84.             if(candyCount > 0){
  85.                 gumballMachine.setState(gumballMachine.noQuarterState);
  86.                 return;
  87.             }
  88.         }

  89.         System.out.println("抱歉,糖果已售尽");
  90.         gumballMachine.setState(gumballMachine.soldOutState);
  91.     }

  92. }

  93. /**
  94.  * 售尽的状态
  95.  */
  96. public class SoldOutState extends State{
  97.     GumballMachine gumballMachine;

  98.     public SoldOutState(GumballMachine gumballMachine) {
  99.         this.gumballMachine = gumballMachine;
  100.     }
  101.     @Override
  102.     public void insertQuarter() {
  103.         System.out.println("糖果已经售尽");
  104.         returnQuarter();
  105.     }

  106.     @Override
  107.     public void ejectQuarter() {
  108.         System.out.println("没有投币,无法退币");
  109.     }

  110.     @Override
  111.     public void turnCrank() {
  112.         System.out.println("糖果已经售尽");
  113.     }

  114.     @Override
  115.     public void dispense() {
  116.         System.out.println("糖果已经售尽");
  117.     }

  118. }

将糖果机的动作委托到状态类

点击(此处)折叠或打开

  1. public class GumballMachine extends State{
  2.     public State noQuarterState = new NoQuarterState(this);
  3.     public State hasQuarterState = new HasQuarterState(this);
  4.     public State soldState = new SoldState(this);
  5.     public State soldOutState = new SoldOutState(this);

  6.     private State state = soldOutState;
  7.     private int candyCount = 0;

  8.     public GumballMachine(int count) {
  9.         this.candyCount = count;
  10.         if(count > 0)
  11.             setState(noQuarterState);
  12.     }

  13.     @Override
  14.     public void insertQuarter() {
  15.         state.insertQuarter();
  16.     }
  17.     @Override
  18.     public void ejectQuarter() {
  19.         state.ejectQuarter();
  20.     }
  21.     @Override
  22.     public void turnCrank() {
  23.         state.turnCrank();
  24.     }
  25.     @Override
  26.     public void dispense() {
  27.         state.dispense();
  28.     }

  29.     public void setState(State state) {
  30.         this.state = state;
  31.     }

  32.     public State getState() {
  33.         return state;
  34.     }

  35.     public void setCandyCount(int candyCount) {
  36.         this.candyCount = candyCount;
  37.     }

  38.     public int getCandyCount() {
  39.         return candyCount;
  40.     }

  41. }

可以发现,这种设计下,糖果机根本不需要清楚状态的改变,它只用调用状态的方法就行。状态的改变是在状态内部发生的。这就是"状态模式"。


如果此时再增加一种状态,糖果机不需要做任何改变,我们只需要再增加一个状态类,然后在相关的状态类方法里面增加转换的过程即可。


阅读(1402) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~