HeadFir st 设计模式学习笔记11——状态模式

HeadFir st 设计模式学习笔记11——状态模式
1.我们这次的话题是要实现⼀个糖果机,这个糖果机中如下的⼀些状态以指导糖果机的转移:
我们根据这个状态机写了⼀个有i f . .el se为主体的状态机程序,对每⼀个状态的转入转出做了实现

pu bl i c  cl ass Gu mbal l Mach i n e  {
f i n al   stat i c  i n t  SOLD_OUT  = 0;
f i n al   stat i c  i n t  NO_QUARTER = 1;
f i n al   stat i c  i n t  HAS_QUARTER = 2;
f i n al   stat i c  i n t  SOLD = 3;
i n t   state = SOLD_OUT ;
i n t   cou n t  = 0;
pu bl i c Gu mbal l Mach i n e(i n t   cou n t )  {
th i s. cou n t  =  cou n t ;
i f   (cou n t  > 0)  {
state = NO_QUARTER;
}
}
pu bl i c  v oi d  i n sertQu arter()  {
i f   (state == HAS_QUARTER)  {
Sy stem.ou t .pri n t l n ("Y ou   can 't   i n sert  an oth er qu arter");
} el se  i f   (state == NO_QUARTER)  {
state = HAS_QUARTER;
Sy stem.ou t .pri n t l n ("Y ou   i n serted a qu arter");
} el se  i f   (state == SOLD_OUT)  {
Sy stem.ou t .pri n t l n ("Y ou   can 't   i n sert  a qu arter ,   th e mach i n e  i s  sol d ou t ");
} el se  i f   (state == SOLD)  {
Sy stem.ou t .pri n t l n ("Pl ease wai t ,  we're al ready  gi v i n g  y ou  a gu mbal l ");
}
}
pu bl i c  v oi d ej ectQu arter()  {
i f   (state == HAS_QUARTER)  {
Sy stem.ou t .pri n t l n ("Qu arter  retu rn ed");
state = NO_QUARTER;
} el se  i f   (state == NO_QUARTER)  {
Sy stem.ou t .pri n t l n ("Y ou  h av en 't   i n serted a qu arter");
} el se  i f   (state == SOLD)  {
Sy stem.ou t .pri n t l n ("Sorry ,   y ou  al ready   tu rn ed  th e  cran k");
} el se  i f   (state == SOLD_OUT)  {
Sy stem.ou t .pri n t l n ("Y ou   can 't  ej ect ,   y ou  h av en 't   i n serted a qu arter  y et ");
}
}
pu bl i c  v oi d  tu rn Cran k()  {
i f   (state == SOLD)  {
Sy stem.ou t .pri n t l n ("T u rn i n g  twi ce doesn 't  get   y ou  an oth er gu mbal l ! ");
} el se  i f   (state == NO_QUARTER)  {
Sy stem.ou t .pri n t l n ("Y ou   tu rn ed bu t   th ere's n o qu arter");
} el se  i f   (state == SOLD_OUT)  {
Sy stem.ou t .pri n t l n ("Y ou   tu rn ed,  bu t   th ere are n o gu mbal l s");
} el se  i f   (state == HAS_QUARTER)  {
Sy stem.ou t .pri n t l n ("Y ou   tu rn ed. . . ");
state = SOLD;
di spen se();
}
}
pu bl i c  v oi d di spen se()  {
i f   (state == SOLD)  {
Sy stem.ou t .pri n t l n ("A gu mbal l   comes  rol l i n g ou t   th e  sl ot ");
cou n t  =  cou n t   - 1;
i f   (cou n t  == 0)  {
Sy stem.ou t .pri n t l n ("Oops,  ou t  of  gu mbal l s! ");
state = SOLD_OUT ;
} el se  {
state = NO_QUARTER;
}
} el se  i f   (state == NO_QUARTER)  {
Sy stem.ou t .pri n t l n ("Y ou  n eed  to pay   f i rst ");
} el se  i f   (state == SOLD_OUT)  {
Sy stem.ou t .pri n t l n ("No gu mbal l  di spen sed");
} el se  i f   (state == HAS_QUARTER)  {
Sy stem.ou t .pri n t l n ("No gu mbal l  di spen sed");
}
}
pu bl i c  v oi d  ref i l l (i n t  n u mGu mBal l s)  {
th i s. cou n t  = n u mGu mBal l s;
state = NO_QUARTER;
}
pu bl i c St ri n g  toSt ri n g()  {
}
}
但是现在这个公司有了新的要求:当曲柄被转动时,有10%的几率掉下来的是两颗糖——这有
点gambl i n g的意思,但这只是⼀个游戏。
这个时候我们发现⼀切都变得如此郁闷,我们不但要加入⼀个“赢家”的状态, 还有在每⼀个动作
中都要加入⼀些判断是不是在这个状态的判定和后续动作定义。这并不是我们想要的。
2.对于此,我们的设计应该首先定义⼀个State接口,在这个接口中,糖果机的每⼀个动作都有
⼀个对应的方法。在设计这个接口时,我们的原则是:我们倾向于使用抽象类,这样想加入新
的方法就容易的多,但是没有共同的功能(的实现)可以放入其中的则用接口。
pu bl i c  i n terface State  {
pu bl i c  v oi d  i n sertQu arter();
pu bl i c  v oi d ej ectQu arter();
pu bl i c  v oi d  tu rn Cran k();
pu bl i c  v oi d di spen se();
}
然后我们对每个状态实现状态类,我们将动作委托到状态类中。
例如:没有投币的状态NoQu arterState
pu bl i c  cl ass NoQu arterState  i mpl emen t s State  {
Gu mbal l Mach i n e gu mbal l Mach i n e;
pu bl i c NoQu arterState(Gu mbal l Mach i n e gu mbal l Mach i n e)  {
th i s.gu mbal l Mach i n e = gu mbal l Mach i n e;
}
pu bl i c  v oi d  i n sertQu arter()  {
Sy stem.ou t .pri n t l n ("Y ou   i n serted a qu arter");
gumbal lMachine.setState(gumbal lMachine.getHasQuarterState()); / /状态转移
}
pu bl i c  v oi d ej ectQu arter()  {
Sy stem.ou t .pri n t l n ("Y ou  h av en 't   i n serted a qu arter");
}
pu bl i c  v oi d  tu rn Cran k()  {
Sy stem.ou t .pri n t l n ("Y ou   tu rn ed,  bu t   th ere's n o qu arter");
}
pu bl i c  v oi d di spen se()  {
Sy stem.ou t .pri n t l n ("Y ou  n eed  to pay   f i rst ");
}
pu bl i c St ri n g  toSt ri n g()  {
retu rn   "wai t i n g  for qu arter";
}
}
3.我们将这些状态整合到⼀个类中——糖果机类,在这个类的设计中分为两大部分——对于状态
的set ter和get ter,对于状态方法的封装(委托给当前的类)。另外这样我们就得到⼀个糖果机
了:
pu bl i c  cl ass Gu mbal l Mach i n e  {
State  sol dOu tState;
State n oQu arterState;
State h asQu arterState;
State  sol dState;
State state = soldOutState; / /当前状态
i n t   cou n t  = 0;
pu bl i c Gu mbal l Mach i n e(i n t  n u mberGu mbal l s)  {/ /开始的时候就用这个糖果机的类创建好各个状
态以便后边的时候
sol dOu tState = n ew Sol dOu tState(th i s);
n oQu arterState = n ew NoQu arterState(th i s);
h asQu arterState = n ew HasQu arterState(th i s);
sol dState = n ew Sol dState(th i s);
th i s. cou n t  = n u mberGu mbal l s;
i f   (n u mberGu mbal l s > 0)  {
state = n oQu arterState;
}
}
pu bl i c  v oi d  i n sertQu arter()  {
state. i n sertQu arter();
}
pu bl i c  v oi d ej ectQu arter()  {
state.ej ectQu arter();
}
pu bl i c  v oi d  tu rn Cran k()  {
state. tu rn Cran k();
state.di spen se();
}
v oi d  setState(State  state)  {
th i s. state =  state;
}
v oi d  rel easeBal l ()  {
Sy stem.ou t .pri n t l n ("A gu mbal l   comes  rol l i n g ou t   th e  sl ot . . . ");
i f   (cou n t   ! = 0)  {
cou n t  =  cou n t   - 1;
}
}
i n t  getCou n t ()  {
retu rn   cou n t ;
}
v oi d  ref i l l (i n t   cou n t )  {
th i s. cou n t  =  cou n t ;
state = n oQu arterState;
}
pu bl i c State getState()  {
retu rn   state;
}
pu bl i c State getSol dOu tState()  {
retu rn   sol dOu tState;
}
pu bl i c State getNoQu arterState()  {
retu rn  n oQu arterState;
}
pu bl i c State getHasQu arterState()  {
retu rn  h asQu arterState;
}
pu bl i c State getSol dState()  {
retu rn   sol dState;
}
pu bl i c St ri n g  toSt ri n g()  {
}
}
我们现在写⼀个测试类,用这个糖果机买点糖果出来,并测试⼀下这个机器的逻辑是不是正确

pu bl i c  cl ass Gu mbal l Mach i n eT estDri v e  {
pu bl i c  stat i c  v oi d mai n (St ri n g[ ]  args)  {
Gu mbal l Mach i n e gu mbal l Mach i n e = n ew Gu mbal l Mach i n e(5);
Sy stem.ou t .pri n t l n (gu mbal l Mach i n e);
gu mbal l Mach i n e. i n sertQu arter();
gu mbal l Mach i n e. tu rn Cran k();
Sy stem.ou t .pri n t l n (gu mbal l Mach i n e);
gu mbal l Mach i n e. i n sertQu arter();
gu mbal l Mach i n e. tu rn Cran k();
gu mbal l Mach i n e. i n sertQu arter();
gu mbal l Mach i n e. tu rn Cran k();
Sy stem.ou t .pri n t l n (gu mbal l Mach i n e);
}
}
4.这时,我们再考虑那个10%中奖率的小游戏就简单许多了。我们在Gu mbal l Mach i n e 中加入⼀
个新的状态wi n n erState,虽然我们可以把这个逻辑放入原来发放糖果的状态,但是就违背了“⼀
个类,⼀个职责”的原则:
pu bl i c  cl ass Wi n n erState  i mpl emen t s State  {
Gu mbal l Mach i n e gu mbal l Mach i n e;
pu bl i c Wi n n erState(Gu mbal l Mach i n e gu mbal l Mach i n e)  {
th i s.gu mbal l Mach i n e = gu mbal l Mach i n e;
}
pu bl i c  v oi d  i n sertQu arter()  {
Sy stem.ou t .pri n t l n ("Pl ease wai t ,  we're al ready  gi v i n g  y ou  a Gu mbal l ");
}
pu bl i c  v oi d ej ectQu arter()  {
Sy stem.ou t .pri n t l n ("Pl ease wai t ,  we're al ready  gi v i n g  y ou  a Gu mbal l ");
}
pu bl i c  v oi d  tu rn Cran k()  {
Sy stem.ou t .pri n t l n ("T u rn i n g agai n  doesn 't  get   y ou  an oth er gu mbal l ! ");
}
pu bl i c  v oi d di spen se()  {
Sy stem.ou t .pri n t l n ("Y OU'RE  A WI NNER!   Y ou  get   two gu mbal l s  for  y ou r qu arter");
gu mbal l Mach i n e. rel easeBal l ();
i f   (gu mbal l Mach i n e.getCou n t () == 0)  {
gu mbal l Mach i n e. setState(gu mbal l Mach i n e.getSol dOu tState());
} el se  {
gu mbal l Mach i n e. rel easeBal l ();
i f   (gu mbal l Mach i n e.getCou n t () > 0)  {
gu mbal l Mach i n e. setState(gu mbal l Mach i n e.getNoQu arterState());
} el se  {
Sy stem.ou t .pri n t l n ("Oops,  ou t  of  gu mbal l s! ");
gu mbal l Mach i n e. setState(gu mbal l Mach i n e.getSol dOu tState());
}
}
}
pu bl i c St ri n g  toSt ri n g()  {
retu rn   "despen si n g  two gu mbal l s  for  y ou r qu arter ,  becau se  Y OU'RE  A WI NNER! ";
}
}
我们再加入10%成为赢家的逻辑:
pu bl i c  cl ass HasQu arterState  i mpl emen t s State  {
Ran dom  ran domWi n n er = n ew Ran dom(Sy stem. cu rren tT i meMi l l i s());
Gu mbal l Mach i n e gu mbal l Mach i n e;
pu bl i c HasQu arterState(Gu mbal l Mach i n e gu mbal l Mach i n e)  {
th i s.gu mbal l Mach i n e = gu mbal l Mach i n e;
}
pu bl i c  v oi d  i n sertQu arter()  {
Sy stem.ou t .pri n t l n ("Y ou   can 't   i n sert  an oth er qu arter");
}
pu bl i c  v oi d ej ectQu arter()  {
Sy stem.ou t .pri n t l n ("Qu arter  retu rn ed");
gu mbal l Mach i n e. setState(gu mbal l Mach i n e.getNoQu arterState());
}
pu bl i c  v oi d  tu rn Cran k()  {
Sy stem.ou t .pri n t l n ("Y ou   tu rn ed. . . ");
int winner =  randomWinner .nextInt(10);
i f  ((winner == 0) &&  (gumbal lMachine.getCount() > 1))  {
gu mbal l Mach i n e. setState(gu mbal l Mach i n e.getWi n n erState());
} el se  {
gu mbal l Mach i n e. setState(gu mbal l Mach i n e.getSol dState());
}
}
pu bl i c  v oi d di spen se()  {
Sy stem.ou t .pri n t l n ("No gu mbal l  di spen sed");
}
pu bl i c St ri n g  toSt ri n g()  {
retu rn   "wai t i n g  for  tu rn  of   cran k";
}
}
5.我们给这样的状态机设计⼀个名字就是“状态模式”——允许对象在内部状态改变时改变它的行
为,对象在外边看起来就像是修改了它的类⼀样。也就是说,里面的逻辑对外是不可见的,但
同时实际的状态,或者说参与的逻辑又是高度可扩展的。记得,不要让外界直接接触状态,这
样就破坏了面向对象设计的原则。
我们可以拿状态模式与策略模式做个对比,前者在内部定了各个逻辑步骤和状态转移,随着程
序的进行对外呈现不同的样子,这是对多个条件判断的⼀种替代方案,而后者则是在⼀开始由
外部主动指定要组合的策略对象是哪些,这是对继承之外的⼀种替代方案。
6.我们可以把这个设计再提升⼀下:
1) 我们可以把State设计为抽象类,将⼀些方法默认的行为,尤其是出错的行为放入其中,毕竟
有些出错提示是颇具有通用性的,可以供子状态去继承。
2)当我们没有投币时也可以调用转动曲柄的方法,这实际上是不合理的,我们可以通过加入⼀个
布尔类型来屏蔽这⼀bu g。

你可能感兴趣的:(st,HeadFir,设计模式学习笔记1)