Head First 设计模式第十章-状态模式 状态模式(headcount)

网友投稿 559 2022-05-30

状态模式

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

定义状态模式

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

例题

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

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

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

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接口,类图如下:

新的设计想法如下:

首先,我们定义一个State接口,在这个接口内,糖果机的每个动作都有一个对应的方法

然后为机器的每个状态实现状态类,这些类将负责在对应的状态下进行机器的行为

最后,我们要摆脱旧的条件代码,取而代之的方式是,将动作委托到状态类

代码

定义一个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

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

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实例共享。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:从零开始学python | 全面了解带Python的机器人框架(从零开始学python网络爬虫)
下一篇:《考取HCIA证书看我就够了》第四篇:[HCIA-IoT]物联网技术之平台层技术概览
相关文章