• 设计模式之十:状态模式


    状态模式通过改变对象内部的状态来帮助对象控制自己的行为。

    这是一张状态图,其中每个圆圈都是一个状态。

    最简单,第一反应的实现就是使用一个变量来控制状态值,并在方法内书写条件代码来处理不同情况。

    1. package headfirst.designpatterns.state.gumball;
    2. public class GumballMachine {
    3. final static int SOLD_OUT = 0;
    4. final static int NO_QUARTER = 1;
    5. final static int HAS_QUARTER = 2;
    6. final static int SOLD = 3;
    7. int state = SOLD_OUT;
    8. int count = 0;
    9. public GumballMachine(int count) {
    10. this.count = count;
    11. if (count > 0) {
    12. state = NO_QUARTER;
    13. }
    14. }
    15. public void insertQuarter() {
    16. if (state == HAS_QUARTER) {
    17. System.out.println("You can't insert another quarter");
    18. } else if (state == NO_QUARTER) {
    19. state = HAS_QUARTER;
    20. System.out.println("You inserted a quarter");
    21. } else if (state == SOLD_OUT) {
    22. System.out.println("You can't insert a quarter, the machine is sold out");
    23. } else if (state == SOLD) {
    24. System.out.println("Please wait, we're already giving you a gumball");
    25. }
    26. }
    27. public void ejectQuarter() {
    28. if (state == HAS_QUARTER) {
    29. System.out.println("Quarter returned");
    30. state = NO_QUARTER;
    31. } else if (state == NO_QUARTER) {
    32. System.out.println("You haven't inserted a quarter");
    33. } else if (state == SOLD) {
    34. System.out.println("Sorry, you already turned the crank");
    35. } else if (state == SOLD_OUT) {
    36. System.out.println("You can't eject, you haven't inserted a quarter yet");
    37. }
    38. }
    39. public void turnCrank() {
    40. if (state == SOLD) {
    41. System.out.println("Turning twice doesn't get you another gumball!");
    42. } else if (state == NO_QUARTER) {
    43. System.out.println("You turned but there's no quarter");
    44. } else if (state == SOLD_OUT) {
    45. System.out.println("You turned, but there are no gumballs");
    46. } else if (state == HAS_QUARTER) {
    47. System.out.println("You turned...");
    48. state = SOLD;
    49. dispense();
    50. }
    51. }
    52. private void dispense() {
    53. if (state == SOLD) {
    54. System.out.println("A gumball comes rolling out the slot");
    55. count = count - 1;
    56. if (count == 0) {
    57. System.out.println("Oops, out of gumballs!");
    58. state = SOLD_OUT;
    59. } else {
    60. state = NO_QUARTER;
    61. }
    62. } else if (state == NO_QUARTER) {
    63. System.out.println("You need to pay first");
    64. } else if (state == SOLD_OUT) {
    65. System.out.println("No gumball dispensed");
    66. } else if (state == HAS_QUARTER) {
    67. System.out.println("No gumball dispensed");
    68. }
    69. }
    70. public void refill(int numGumBalls) {
    71. this.count = numGumBalls;
    72. state = NO_QUARTER;
    73. }
    74. public String toString() {
    75. StringBuffer result = new StringBuffer();
    76. result.append("\nMighty Gumball, Inc.");
    77. result.append("\nJava-enabled Standing Gumball Model #2004\n");
    78. result.append("Inventory: " + count + " gumball");
    79. if (count != 1) {
    80. result.append("s");
    81. }
    82. result.append("\nMachine is ");
    83. if (state == SOLD_OUT) {
    84. result.append("sold out");
    85. } else if (state == NO_QUARTER) {
    86. result.append("waiting for quarter");
    87. } else if (state == HAS_QUARTER) {
    88. result.append("waiting for turn of crank");
    89. } else if (state == SOLD) {
    90. result.append("delivering a gumball");
    91. }
    92. result.append("\n");
    93. return result.toString();
    94. }
    95. }

    以上的代码最大的问题就是没有遵守开发-关闭原则,一遇到新的需求(投币后有10%的概率出现“赢家”状态,给出2颗糖果)就需要修改源代码,重新整理所有代码的逻辑。

    重构后的代码理念:

    • 定义一个State接口,糖果机器的每个动作都在接口中有一个对应的方法。
    • 为机器中的每个状态实现一个状态类。这些类将负责在对应状态下进行机器的行为。
    • 将动作委托到状态类。

    1. // 每种状态的各个方法的行为都不一样
    2. NoQuarterState
    3. {
    4. insertQuarter() // 转到HasQuarterState
    5. ejectQuarter() // 未投入25分钱
    6. turnCrank() // 未投入25分钱,转动曲柄无效
    7. dispense() // 未投入25分钱,不能分发糖果
    8. }

    在新的糖果机中,我们不使用静态整数,而使用state对象。

    1. public class GumballMachine {
    2. // 所有的状态对象都在构造器中创建并赋值
    3. State soldOutState;
    4. State noQuarterState;
    5. State hasQuarterState;
    6. State soldState;
    7. State state;
    8. int count = 0;
    9. public GumballMachine(int numberGumballs) {
    10. soldOutState = new SoldOutState(this);
    11. noQuarterState = new NoQuarterState(this);
    12. hasQuarterState = new HasQuarterState(this);
    13. soldState = new SoldState(this);
    14. this.count = numberGumballs;
    15. if (numberGumballs > 0) {
    16. state = noQuarterState;
    17. } else {
    18. state = soldOutState;
    19. }
    20. }
    21. public void insertQuarter() {
    22. state.insertQuarter();
    23. }
    24. public void ejectQuarter() {
    25. state.ejectQuarter();
    26. }
    27. public void turnCrank() {
    28. state.turnCrank();
    29. state.dispense();
    30. }
    31. void releaseBall() {
    32. System.out.println("A gumball comes rolling out the slot...");
    33. if (count > 0) {
    34. count = count - 1;
    35. }
    36. }
    37. int getCount() {
    38. return count;
    39. }
    40. void refill(int count) {
    41. this.count += count;
    42. System.out.println("The gumball machine was just refilled; its new count is: " + this.count);
    43. state.refill();
    44. }
    45. void setState(State state) {
    46. this.state = state;
    47. }
    48. public State getState() {
    49. return state;
    50. }
    51. public State getSoldOutState() {
    52. return soldOutState;
    53. }
    54. public State getNoQuarterState() {
    55. return noQuarterState;
    56. }
    57. public State getHasQuarterState() {
    58. return hasQuarterState;
    59. }
    60. public State getSoldState() {
    61. return soldState;
    62. }
    63. public String toString() {
    64. StringBuffer result = new StringBuffer();
    65. result.append("\nMighty Gumball, Inc.");
    66. result.append("\nJava-enabled Standing Gumball Model #2004");
    67. result.append("\nInventory: " + count + " gumball");
    68. if (count != 1) {
    69. result.append("s");
    70. }
    71. result.append("\n");
    72. result.append("Machine is " + state + "\n");
    73. return result.toString();
    74. }
    75. }

    现在我们已经可以:

    • 将每个状态的行为局部化到它自己的类中。
    • 将容易产生问题的if语句删除,以方便日后的维护。
    • 让每个状态“对修改关闭”,让糖果机“对扩展开放”(可以加入新的状态类)

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

    策略模式和状态模式的类图是一样的(回去翻了下书,好像没瞅到),但

    我们把策略模式想成是除了继承之外的一种弹性替代方案。如果使用继承定义一个类的行为,则会被这个行为困住,很难修改。

    状态模式是不用在context中放置许多条件判断的替代方案。通过将行为包装进状态对象中,可以通过在context内简单改变状态对象来改变context的行为。

    在GumballMachine中,状态决定了下一个状态应该是什么。ConcreteState总是决定接下来的状态是什么吗?

    状态转换是固定的时候,就适合放在Context中。转换是更动态的时候,通常就会放在状态类中。

    1. // GumballMachine的修改和WinnerState的实现是很简单的
    2. // 这里就只将HasQuarterState列出
    3. import java.util.Random;
    4. public class HasQuarterState implements State {
    5. Random randomWinner = new Random(System.currentTimeMillis());
    6. GumballMachine gumballMachine;
    7. public HasQuarterState(GumballMachine gumballMachine) {
    8. this.gumballMachine = gumballMachine;
    9. }
    10. public void insertQuarter() {
    11. System.out.println("You can't insert another quarter");
    12. }
    13. public void ejectQuarter() {
    14. System.out.println("Quarter returned");
    15. gumballMachine.setState(gumballMachine.getNoQuarterState());
    16. }
    17. public void turnCrank() {
    18. System.out.println("You turned...");
    19. int winner = randomWinner.nextInt(10);
    20. if ((winner == 0) && (gumballMachine.getCount() > 1)) {
    21. gumballMachine.setState(gumballMachine.getWinnerState());
    22. } else {
    23. gumballMachine.setState(gumballMachine.getSoldState());
    24. }
    25. }
    26. public void dispense() {
    27. System.out.println("No gumball dispensed");
    28. }
    29. public void refill() { }
    30. public String toString() {
    31. return "waiting for turn of crank";
    32. }
    33. }

    ------------------

  • 相关阅读:
    AI大数据统计《庆余年2》中的小人物有哪些?
    如何使用PHP进行表单验证?
    【压缩感知 SDA】A Deep Learning Approach to Structured Signal Recovery
    Array and Set work process
    【算法 - 动态规划】最长回文子序列
    【PAT甲级】1016 Phone Bills
    银行卡OCR API推荐
    深入浅出的给大家分析下现在做抖音短视频还来得及吗?
    海外仓物流有哪些优缺点
    自建应用-企业微信-侧边栏开发配置
  • 原文地址:https://blog.csdn.net/weixin_41126303/article/details/132953932