《Head First 设计模式》第十章-状态模式 状态模式

状态模式

策略模式和状态模式是双胞胎,在出生时才分开。你已经知道,策略模式是围绕可以互换的算法来创建成功业务的,然而,状态走的是更崇高的路,它通过改变对象内部的状态来帮助对象控制自己的行为。

定义状态模式

先看看定义:状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类

例题

自动糖果售卖机,糖果机的控制器需要的工作流程如下图

糖果机工作流程图

从上面的状态图中可以找到所有的状态:

所有状态

我们可以创建一个实例变量来持有目前的状态,然后定义每个状态的值:

1

2

3

4

5

6

7

//每个状态用不同的值表示

final static int SOLD_OUT=0;//售罄

final static int NO_QUARTER=1;//没有投币

final static int HAS_QUARTER=2;//已投币

final static int SOLD=3;//售出糖果

//实例变量持有当前状态,只要改变变量值状态也会随之改变

int state =SOLD_OUT;

现在,我们将所有系统中可以发生的动作整合起来:
“投入25分钱”,“退回25分钱”,“转动曲柄”,“发放糖果”
这些动作是糖果机的接口,这是你能对糖果机做的事情,
调用任何一个动作都会造成状态的转换,
发放糖果更多是糖果机的内部动作,机器自己调用自己。

我们创建一个类,它的作用就像是一个状态机,每一个动作,我们都创建了一个对应的方法,这些方法利用条件语句来决定在每个状态内什么行为是恰当的。比如对“投入25分钱”这个动作来说,我们可以把对应方法写成下面的样子:

1

2

3

4

5

6

7

8

9

10

11

12

13

public void insertQuarter(){

    if(state==HAS_QUARTER){

        //每个状态对应的行为

         ......

    }else if(state==SOLD_OUT){

        ......

    }else if(state ==SOLD){

        ......

    }else if(state==NO_QUARTER){

        state=HAS_QUARTER;//状态转换

        ......

    }

}

初步代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

class GumballMachine{

    final static int SOLD_OUT=0;

    final static int NO_QUARTER=1;

    final static int HAS_QUARTER=2;

    final static int SOLD=3;

    int state =SOLD_OUT;

    int count =0;//存储糖果数量

    public  GumballMachine(int count){

        this.count=count;

        if(count>0){

            state=NO_QUARTER;

        }

    }

    //当有25分钱投入,就会执行这个方法

    public void insertQuarter(){

        if(state==HAS_QUARTER){

            System.out.println("如果已投入过25分钱,我们就告诉顾客");

        }else if(state==NO_QUARTER){

            state=HAS_QUARTER;

            System.out.println("如果是在“没有25分钱”的状态下,我们就接收25分钱," +"并将状态转换到“有25分钱”的状态");

        }else if(state ==SOLD_OUT){

            System.out.println("如果糖果已经售罄,我们就拒绝收钱");

        }else if(state==SOLD){

            System.out.println("如果顾客刚才买了糖果,就需要稍等一下,好让状态转换完毕。" +"恢复到“没有25分钱”的状态");

            state=NO_QUARTER;

        }

    }

    //如果顾客试着退回25分钱就执行这个方法

    public void ejectQuarter(){

        if(state==HAS_QUARTER){

            System.out.println("如果有25分钱,我们就把钱退出来,回到“没有25分钱”的状态");

            state=NO_QUARTER;

        }else if(state==NO_QUARTER){

            System.out.println("如果没有25分钱的话,当然不能退出25分钱");

        }else if(state ==SOLD){

            System.out.println("顾客已经转动曲柄就不能再退钱了,他已经拿到糖果了");

        }else if(state==SOLD_OUT){

            System.out.println("如果糖果售罄,就不能接受25分钱,当然也不可能退钱");

        }

    }

    //顾客试着转动曲柄

    public void turnCrank(){

        if(state==SOLD){

            System.out.println("别想骗过机器拿两次糖果");

        }else if(state==NO_QUARTER){

            System.out.println("我们需要先投入25分钱");

        }else if(state ==SOLD_OUT){

            System.out.println("我们不能给糖果,已经没有任何糖果了");

        }else if(state==HAS_QUARTER){

            System.out.println("成功,他们拿到糖果了," +"改变状态到“售出糖果”然后调用机器的disoense()方法");

            state=SOLD;

            dispense();

        }

    }

    //调用此方法,发放糖果

    public void dispense(){

        if(state==SOLD){

            System.out.println("我们正在“出售糖果”状态,给他们糖果");

            count=count-1;

            /*

            我们在这里处理“糖果售罄”的情况,如果这是最后一个糖果,将机器的状态设置到“糖果售罄”否则就回到“没有25分钱”的状态

             */

            if(count==0){

                System.out.println();

                state=SOLD_OUT;

            }else{

                state=NO_QUARTER;

            }

        }else if(state==SOLD_OUT){

            System.out.println("这些都不应该发生,但是如果做了,就得到错误提示");

        }else if(state ==HAS_QUARTER){

            System.out.println("这些都不应该发生,但是如果做了,就得到错误提示");

        }else if(state==NO_QUARTER){

            System.out.println("这些都不应该发生,但是如果做了,就得到错误提示");

        }

    }

}

尽管程序完美运行,但还是躲不掉需求变更的命运

现在糖果公司要求:当曲柄被转动时,有10%的几率掉下来的是两个糖果。(氪金扭蛋)

再回看一下我们的初步代码,想要实现新的需求将会变得非常麻烦:

  • 必须新增一个中奖的“赢家”状态。
  • 必须在每一个方法添加新的判断条件来处理“赢家”状态。
  • 转动把手的方法中还需要检查目前状态是否是“赢家”再决定切换到“赢家”状态行为还是正常出售行为。

在现有代码基础上做增加将会很麻烦,也不利与以后的维护,扩展性差。

回顾一下第一章的策略模式中的设计原则:

找出应用中可能需要变化之处,把他们独立出来

将状态独立出来,封装成一个类,都实现State接口,类图如下:

项目类图

新的设计想法如下:

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

代码

定义一个State接口

1

2

3

4

5

6

public interface State {

    public void insertQuarter();//投币

    public void ejectQuarter();//退币

    public void turnCrank();//转动出货把手

    public void dispense();//出售

}

为机器的每个状态实现状态类:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

//未投币状态

public class NoQuarterState implements State {

    GumballMachine gumballMachine;

    public NoQuarterState(GumballMachine gumballMachine) {

        this.gumballMachine=gumballMachine;

    }

    public void insertQuarter() {

        System.out.println("你投入一枚硬币");

        gumballMachine.setState(gumballMachine.getHasQuarterState());//状态转换为已投币状态

    }

    public void ejectQuarter() {

        System.out.println("你未投币,无法退钱");

    }

    public void turnCrank() {

        System.out.println("未投币,请先投币");

    }

    public void dispense() {

        System.out.println("请先投币");

    }

}

 

//已投币状态

public class HasQuarterState implements State {

    Random randomWinner=new Random(System.currentTimeMillis());

    GumballMachine gumballMachine;

    public HasQuarterState(GumballMachine gumballMachine) {

        this.gumballMachine=gumballMachine;

    }

    public void insertQuarter() {

        System.out.println("已投币,无法再接收投币");

    }

    public void ejectQuarter() {

        System.out.println("已退币");

        gumballMachine.setState(gumballMachine.getNoQuarterState());

    }

    public void turnCrank() {

        System.out.println("已转动把手,糖果出售中。。。。");

        int winner=randomWinner.nextInt(10);//随机数生成,用以标记“赢家”状态

        if((winner==0)&&(gumballMachine.getCount()>1))

            gumballMachine.setState(gumballMachine.getWinnerState());

        else

            gumballMachine.setState(gumballMachine.getSoldState());

    }

    public void dispense() {

        System.out.println("机器中已经没有糖果可以出售了!");

    }

}

 

//出售状态

public class SoldState implements State {

    GumballMachine gumballMachine;

    public SoldState(GumballMachine gumballMachine) {

        this.gumballMachine=gumballMachine;

    }

    public void insertQuarter() {

        System.out.println("请等候,正在初始化机器中");

    }

    public void ejectQuarter() {

        System.out.println("抱歉,您已转动把手获得了糖果,无法退币");

    }

    public void turnCrank() {

        System.out.println("您重复转动把手,无法再获取更多糖果");

    }

    public void dispense() {

        gumballMachine.releaseBall();//出货,糖果-1

        if(gumballMachine.getCount()>0)

            gumballMachine.setState(gumballMachine.getNoQuarterState());

        else {

            System.out.println("糖果已售完");

            gumballMachine.setState(gumballMachine.getSoldOutState());

        }   

    }

}

 

//售罄状态

public class SoldOutState implements State {

    GumballMachine gumballMachine;

    public SoldOutState(GumballMachine gumballMachine) {

        this.gumballMachine=gumballMachine;

    }

    public void insertQuarter() {

        System.out.println("此机器的糖果已售完,不接收投币");

    }

    public void ejectQuarter() {

        System.out.println("未投币,退币失败");

    }

    public void turnCrank() {

        System.out.println("糖果已售完,转动把手也不会有糖果出来的");

    }

    public void dispense() {

        System.out.println("机器中已无糖果");

    }

}

 

//赢家状态

public class WinnerState implements State {

    GumballMachine gumballMachine;

    public WinnerState(GumballMachine gumballMachine) {

        this.gumballMachine=gumballMachine;

    }

    public void insertQuarter() {

        System.out.println("请等候,正在初始化机器中");

    }

    public void ejectQuarter() {

        System.out.println("抱歉,您已转动把手获得了糖果");

    }

    public void turnCrank() {

        System.out.println("您重复转动把手,无法再获取更多糖果");

    }

    public void dispense() {

        System.out.println("恭喜你成为幸运儿,你将额外获得一个免费糖果");

        gumballMachine.releaseBall();//出货,糖果-1

        if(gumballMachine.getCount()==0)

            gumballMachine.setState(gumballMachine.getSoldOutState());

        else {

            gumballMachine.releaseBall();

            if(gumballMachine.getCount()>0)

                gumballMachine.setState(gumballMachine.getNoQuarterState());

            else {

                System.out.println("糖果已售完");

                gumballMachine.setState(gumballMachine.getSoldOutState());

            }

        }

    }

}

糖果机类:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

public class GumballMachine {

    State soldOutState;

    State noQuarterState;

    State hasQuarterState;

    State soldState;

    State winnerState;

 

    State state=soldOutState;

    int count=0;

 

    public GumballMachine(int numberGumballs) {//初始化

        soldOutState=new SoldOutState(this);

        noQuarterState=new NoQuarterState(this);

        hasQuarterState=new HasQuarterState(this);

        soldState=new SoldState(this);

        winnerState=new WinnerState(this);

 

        this.count=numberGumballs;

        if(numberGumballs>0)

            state=noQuarterState;//先判断条件再改变状态

    }

    //将动作委托到状态类

    public void insterQuarter() {

        state.insertQuarter();

    }

    public void ejectQuarter() {

        state.ejectQuarter();

    }

    public void turnCrank() {

        state.turnCrank();

        state.dispense();

    }

    //获取当前状态

    public State getHasQuarterState() {

        return hasQuarterState;

    }

    //改变状态

    public void setState(State state) {

        this.state=state;

    }

    public void releaseBall() {

        System.out.println("糖果从出口售出");

        if(count!=0)

            count-=1;

    }

    public State getSoldOutState() {

        return soldOutState;

    }

    public State getNoQuarterState() {

        return noQuarterState;

    }

    public State getSoldState() {

        return soldState;

    }

    //获取糖果机中糖果数量

    public int getCount() {

        return count;

    }

 

    public State getWinnerState() {

        return winnerState;

    }

    public String toString() {

        // TODO 自动生成的方法存根

        String s="剩余糖果:"+count;

        return s;

    }

}

以上就是用状态模式实现的,仔细观察你会发现状态模式其实和策略模式很像,来看看状态模式的类图:

状态模式类图

状态模式的类图其实和策略模式完全一样!

状态模式与策略模式

这两个模式的差别在于它们的“意图”

  • 以状态模式而言,我们将一群行为封装在状态对象中,context的行为随时可委托到那些状态对象中的一个,随着时间而流逝,当前状态在状态对象集合中游走改变,以反映出context内部的状态,因此,context的行为也会跟着改变,但是context的客户对于状态对象了解不多,甚至根本是浑然不觉。
  • 以策略模式而言,客户通常主动指定Context所要组合的策略对象时哪一个。现在,固然策略模式让我们具有弹性,能够在运行时改变策略,但对于某个context对象来说,通常都只有一个最适当的策略对象。
  • 一般的,我们把策略模式想成是除了继承之外的一种弹性替代方案,如果你使用继承定义了一个类的行为,你将被这个行为困住,是指要修改它都很难,有了策略模式,你可以通过组合不同的对象来改变行为。
  • 我们把状态模式想成是不用在context中放置许多条件判断的替代方案,通过将行为包装进状态对象中,你可以通过在context内简单地改变状态对象来改变context的行为。

模式区分

状态模式:封装基于状态的行为,并将行为委托到当前状态

策略模式:将可以互换的行为封装起来。然后使用委托的方法,觉得使用哪一个行为

模板方法模式:由子类决定如何实现算法中的某些步骤

要点

(1)状态模式允许一个对象基于内部状态而拥有不同的行为。

(2)和程序状态机(PSM)不同,状态模式用类来表示状态。

(3)Context会将行为委托给当前状态对象。

(4)通过将每一个状态封装进一个类,我们把以后需要做的任何改变局部化了。

(5)状态模式和策略模式有相同的类图,但是他们的意图不同。

(6)策略模式通常会用行为或算法配置Context类。

(7)状态模式允许Context随着状态的改变而改变行为。

(8)状态转换可以有State类或Context类控制。

(9)使用状态模式通常会导致设计中类的数目大量增加。

(10)状态栏可以被多个Context实例共享。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/445344.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

推荐算法--利用用户标签数据(04)

文章目录流行的推荐系统通过3种方式联系用户兴趣和物品 (1):利用用户喜欢过的物品,给用户推荐与他喜欢过的物品相似的物品,这是基于物品的算法。 (2):利用和用户兴趣相似的其他用户…

Python(5)-注释

Python注释1.单行注释2. 多行注释(块注释)3.注释的使用和代码规范pyhton 的注释 使用自己熟悉的语言(中文),解释代码。Python解释器在执行文件时不会执行井号右边边的内容。1.单行注释 # 井号后面跟着注释内容 灰灰的虚…

网络原理知识点总结

第一章: 计算机网络系统由资源子网和通信子网组成。 计算机网络系统主要由网络通信系统、操作系统和应用系统构成 互联网基础结构发展的三个阶段: 第一阶段:从单个网络 ARPANET 向互联网发展的过程。 第二阶段:建成了三级结构…

推荐算法--时效性(05)

时效性 推荐系统应该考虑时间效应,因为用户的兴趣是有时间变化的。用户一年前喜欢的东西现在不一定感兴趣,相比于推荐过去喜欢的物品,推荐用户近期喜欢的物品更有参考价值。而在新闻更是如此,推荐过去跟用户兴趣一致的新闻已经失去…

推荐算法--推荐系统架构(06)

外围架构一般来说,每个网站都有一个 UI 系统,UI 系统负责给用户展示网页并和用户交互。网站会通过日志系统将用户在 UI 上的各种各样的行为记录到用户行为日志中。 从上面的结构可以看到,除了推荐系统本身,主要还依赖两个条件--界…

Python(6)-算数运算符

算数运算符1.算数运算符2.优先级1.算数运算符 加 减- 乘* 除/ 取商// 取余数% 幂**(能算n次方: 2**38,一直以为只能算平方) 扩展: 乘法用于字符串:字符串重复指定的次数,要拼接的次数很长时,用乘号很方便…

推荐算法--其他信息(07)

文章目录目录1.利用上下文信息1.1时间上下文1.2地点上下文2.利用网络社交数据2.1 获取网络社交数据途径2.2 社交网络数据2.3 基于社交网络的推荐2.4 推荐算法2.5 给用户推荐好友目录 1.利用上下文信息 1.1时间上下文 用户的兴趣是随着时间变化的,三天打鱼两天晒网…

Python(7)-程序执行的原理

程序执行的原理1.计算机中的三个核心部件2.程序执行的原理3.程序的作用1.计算机中的三个核心部件 CPU:中央处理区,超大规模的集成电路,负责处理数据、计算 内存:临时存储数据,断电数据消失,读取数据快 硬盘…

橙白oj 2017级《算法分析与设计》-练习02

注:A题我以为给新生出的,应该贼简单,是按顺序消灭,卡了十几分钟,成了最后一个ac的题,真是菜的真实。 Problem A: Description 白细胞是人体与疾病斗争的“卫士”。当病菌侵入人体体内时,白细胞…

python(9)-变量、input函数

变量、input函数1.变量的定义(不可变对象)2.变量的类型3.变量的命名规范4. 不同类型的数据计算5.类型转换函数6.input()7.Tips程序是用来处理数据的,而变量是用来存储数据的。 关于函数,是一个提前准备好的代码;可以直接使用,不用…

推荐算法--总结(08)

一、推荐系统结构二、推荐引擎算法(Algorithm)1、协同过滤推荐算法1.1 关系矩阵与矩阵计算1.1.1 用户与用户(U-U矩阵)1.1.2 物品与物品(V-V矩阵)1.1.3 用户与物品(U-V矩阵)1.1.4 奇异…

算法总结-1算法入门

1.0 前言 算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。 …

FM系列算法解读(FM+FFM+DeepFM)

在计算广告中,CTR是非常重要的一环。对于特征组合来说,业界通用的做法主要有两大类:FM系列和Tree系列。这里我们来介绍一下FM系列。   在传统的线性模型中,每个特征都是独立的,如果需要考虑特征与特征之间的相互作用…

二叉树层序遍历

层序遍历序列为:ABCDEFG 思路:栈是先进后出的数据结构,而队列是先进先出的数据结构。 我们层序遍历,很明显,先遇到的节点先打印,不同于前中后序遍历,我们采用队列结构。 具体执行过程如下&…

深度学习(01)-- 基础学习

文章目录目录1. 深度学习基础1.1 深度学习总览1.2 深度网络训练过程1.2.1 传统神经网络的训练方法为什么不能用在深度神经网络1.2.2 deep learning训练过程1.3 数学知识:2. 九种深度学习模型2.1 受限玻尔兹曼机RBM2.2 自编码器AE(降维)2.3 深…

MachineLearning(1)-激活函数sigmoid、损失函数MSE、CrossEntropyLoss

损失函数1.激活函数2.损失函数2.1均方误差损失函数2.2交叉熵损失函数2.3 NLLLoss()2.4 BCELoss()1.激活函数 全连接网络又叫多层感知器,多层感知器的基本单元神经元是模仿人类神经元兴奋与抑制机制,对其输入进行加权求和,若超过某一阈值则该…

Java的IO总结

非流式文件类--File类 从定义看,File类是Object的直接子类,同时它继承了Comparable接口可以进行数组的排序。 File类的操作包括文件的创建、删除、重命名、得到路径、创建时间等,以下是文件操作常用的函数。 File类是对文件系统中文件以及文…

深度学习(02)-- ANN学习

文章目录目录1.神经网络知识概览1.1深度学习顶会1.2相关比赛1.3神经网络知识概览1.4神经网络编程一般实现过程2.简单神经网络ANN2.1 数据集:2.2 网络结构:2.3 代码实现2.3.1 读取数据,并做处理2.3.2 构建网络结构2.3.3 训练网络目录 1.神经网…

python(11)-if语句,断言assert

分支语句if1.if基本语法2 if语句的嵌套3 比较运算符号4 逻辑运算符:5 整数随机数初应用6 tip7.断言assert1.if基本语法 if语句开发中的应用场景:如果条件成立做一件事情,如果条件不成立做另外一件事情。有了if语句,程序有了分支.…

深度学习(03)-- CNN学习

文章目录目录1.CNN学习2.Keras深度学习框架目录 1.CNN学习 卷积神经网络CNN总结 从神经网络到卷积神经网络(CNN)我们知道神经网络的结构是这样的: 那卷积神经网络跟它是什么关系呢?其实卷积神经网络依旧是层级网络,…