1. 意图(Intent)

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

2. 类图(Class Diagram)

79df886f-fdc3-4020-a07f-c991bb58e0d8.png

3. 实现(Implementation)

I 商品存储案例

商品库存中心有个最基本的需求是减库存和补库存,我们看看怎么用状态模式来写。
核心在于,我们的关注点不再是 Context 是该进行哪种操作,而是关注在这个 Context 会有哪些操作。
定义状态接口:[ State ]

  1. public interface State {
  2. public void doAction(Context context);
  3. }

[ ConcreteState ]
定义减库存的状态:

  1. public class DeductState implements State {
  2. public void doAction(Context context) {
  3. System.out.println("商品卖出,准备减库存");
  4. context.setState(this);
  5. //... 执行减库存的具体操作
  6. }
  7. public String toString() {
  8. return "Deduct State";
  9. }
  10. }

定义补库存状态:

  1. public class RevertState implements State {
  2. public void doAction(Context context) {
  3. System.out.println("给此商品补库存");
  4. context.setState(this);
  5. //... 执行加库存的具体操作
  6. }
  7. public String toString() {
  8. return "Revert State";
  9. }
  10. }

前面用到了 context.setState(this),我们来看看怎么定义 Context 类::[ Context ]

  1. public class Context {
  2. private State state;
  3. private String name;
  4. public Context(String name) {
  5. this.name = name;
  6. }
  7. public void setState(State state) {
  8. this.state = state;
  9. }
  10. public void getState() {
  11. return this.state;
  12. }
  13. }

我们来看下客户端调用,大家就一清二楚了:

  1. public static void main(String[] args) {
  2. // 我们需要操作的是 iPhone X
  3. Context context = new Context("iPhone X");
  4. // 看看怎么进行补库存操作
  5. State revertState = new RevertState();
  6. revertState.doAction(context);
  7. // 同样的,减库存操作也非常简单
  8. State deductState = new DeductState();
  9. deductState.doAction(context);
  10. // 如果需要我们可以获取当前的状态
  11. // context.getState().toString();
  12. }

读者可能会发现,在上面这个例子中,如果我们不关心当前 context 处于什么状态,那么 Context 就可以不用维护 state 属性了,那样代码会简单很多。

不过,商品库存这个例子毕竟只是个例,我们还有很多实例是需要知道当前 context 处于什么状态的。

II 糖果销售机案例

糖果销售机有多种状态,每种状态下销售机有不同的行为,状态可以发生转移,使得销售机的行为也发生改变。
396be981-3f2c-4fd9-8101-dbf9c841504b.jpg
[ State ]

  1. public interface State {
  2. // 投入 25 分钱
  3. void insertQuarter();
  4. // 退回 25 分钱
  5. void ejectQuarter();
  6. // 转动曲柄
  7. void turnCrank();
  8. // 发放糖果
  9. void dispense();
  10. }

[ ConcreteState ]
有四分之一状态

  1. public class HasQuarterState implements State {
  2. private GumballMachine gumballMachine; // 口香糖机
  3. public HasQuarterState(GumballMachine gumballMachine) {
  4. this.gumballMachine = gumballMachine;
  5. }
  6. @Override
  7. public void insertQuarter() {
  8. System.out.println("You can't insert another quarter");
  9. }
  10. @Override
  11. public void ejectQuarter() {
  12. System.out.println("Quarter returned");
  13. gumballMachine.setState(gumballMachine.getNoQuarterState());
  14. }
  15. @Override
  16. public void turnCrank() {
  17. System.out.println("You turned...");
  18. gumballMachine.setState(gumballMachine.getSoldState());
  19. }
  20. @Override
  21. public void dispense() {
  22. System.out.println("No gumball dispensed");
  23. }
  24. }

无四分之一状态

  1. public class NoQuarterState implements State {
  2. GumballMachine gumballMachine;
  3. public NoQuarterState(GumballMachine gumballMachine) {
  4. this.gumballMachine = gumballMachine;
  5. }
  6. @Override
  7. public void insertQuarter() {
  8. System.out.println("You insert a quarter");
  9. gumballMachine.setState(gumballMachine.getHasQuarterState());
  10. }
  11. @Override
  12. public void ejectQuarter() {
  13. System.out.println("You haven't insert a quarter");
  14. }
  15. @Override
  16. public void turnCrank() {
  17. System.out.println("You turned, but there's no quarter");
  18. }
  19. @Override
  20. public void dispense() {
  21. System.out.println("You need to pay first");
  22. }
  23. }

售罄状态

  1. public class SoldOutState implements State {
  2. GumballMachine gumballMachine;
  3. public SoldOutState(GumballMachine gumballMachine) {
  4. this.gumballMachine = gumballMachine;
  5. }
  6. @Override
  7. public void insertQuarter() {
  8. System.out.println("You can't insert a quarter, the machine is sold out");
  9. }
  10. @Override
  11. public void ejectQuarter() {
  12. System.out.println("You can't eject, you haven't inserted a quarter yet");
  13. }
  14. @Override
  15. public void turnCrank() {
  16. System.out.println("You turned, but there are no gumballs");
  17. }
  18. @Override
  19. public void dispense() {
  20. System.out.println("No gumball dispensed");
  21. }
  22. }

售出状态

  1. public class SoldState implements State {
  2. GumballMachine gumballMachine;
  3. public SoldState(GumballMachine gumballMachine) {
  4. this.gumballMachine = gumballMachine;
  5. }
  6. @Override
  7. public void insertQuarter() {
  8. System.out.println("Please wait, we're already giving you a gumball");
  9. }
  10. @Override
  11. public void ejectQuarter() {
  12. System.out.println("Sorry, you already turned the crank");
  13. }
  14. @Override
  15. public void turnCrank() {
  16. System.out.println("Turning twice doesn't get you another gumball!");
  17. }
  18. @Override
  19. public void dispense() {
  20. gumballMachine.releaseBall();
  21. if (gumballMachine.getCount() > 0) {
  22. gumballMachine.setState(gumballMachine.getNoQuarterState());
  23. } else {
  24. System.out.println("Oops, out of gumballs");
  25. gumballMachine.setState(gumballMachine.getSoldOutState());
  26. }
  27. }
  28. }

[ Context ]

  1. public class GumballMachine {
  2. private State soldOutState;
  3. private State noQuarterState;
  4. private State hasQuarterState;
  5. private State soldState;
  6. private State state;
  7. private int count = 0; // 标记口香糖个数,由构造函数的 numberGumballs 初始化
  8. public GumballMachine(int numberGumballs) {
  9. count = numberGumballs;
  10. soldOutState = new SoldOutState(this);
  11. noQuarterState = new NoQuarterState(this);
  12. hasQuarterState = new HasQuarterState(this);
  13. soldState = new SoldState(this);
  14. if (numberGumballs > 0) {
  15. state = noQuarterState;
  16. } else {
  17. state = soldOutState;
  18. }
  19. }
  20. public void insertQuarter() {
  21. state.insertQuarter();
  22. }
  23. public void ejectQuarter() {
  24. state.ejectQuarter();
  25. }
  26. public void turnCrank() {
  27. state.turnCrank();
  28. state.dispense();
  29. }
  30. public void setState(State state) {
  31. this.state = state;
  32. }
  33. public void releaseBall() {
  34. System.out.println("A gumball comes rolling out the slot...");
  35. if (count != 0) {
  36. count -= 1;
  37. }
  38. }
  39. public State getSoldOutState() {
  40. return soldOutState;
  41. }
  42. public State getNoQuarterState() {
  43. return noQuarterState;
  44. }
  45. public State getHasQuarterState() {
  46. return hasQuarterState;
  47. }
  48. public State getSoldState() {
  49. return soldState;
  50. }
  51. public int getCount() {
  52. return count;
  53. }
  54. }

客户端:

  1. public class Client {
  2. public static void main(String[] args) {
  3. GumballMachine gumballMachine = new GumballMachine(5);
  4. # 1
  5. gumballMachine.insertQuarter();
  6. gumballMachine.turnCrank();
  7. # 2
  8. gumballMachine.insertQuarter();
  9. gumballMachine.ejectQuarter();
  10. gumballMachine.turnCrank();
  11. # 3
  12. gumballMachine.insertQuarter();
  13. gumballMachine.turnCrank();
  14. # 4
  15. gumballMachine.insertQuarter();
  16. gumballMachine.turnCrank();
  17. gumballMachine.ejectQuarter();
  18. # 5
  19. gumballMachine.insertQuarter();
  20. gumballMachine.insertQuarter();
  21. gumballMachine.turnCrank();
  22. # 6
  23. gumballMachine.insertQuarter();
  24. gumballMachine.turnCrank();
  25. # 7
  26. gumballMachine.insertQuarter();
  27. gumballMachine.turnCrank();
  28. }
  29. }

输出:

  1. #1
  2. You insert a quarter
  3. You turned...
  4. A gumball comes rolling out the slot...
  5. #2
  6. You insert a quarter
  7. Quarter returned
  8. You turned, but there's no quarter
  9. You need to pay first
  10. #3
  11. You insert a quarter
  12. You turned...
  13. A gumball comes rolling out the slot...
  14. #4
  15. You insert a quarter
  16. You turned...
  17. A gumball comes rolling out the slot...
  18. You haven't insert a quarter
  19. #5
  20. You insert a quarter
  21. You can't insert another quarter
  22. You turned...
  23. A gumball comes rolling out the slot...
  24. #6
  25. You insert a quarter
  26. You turned...
  27. A gumball comes rolling out the slot...
  28. Oops, out of gumballs
  29. #7
  30. You can't insert a quarter, the machine is sold out
  31. You turned, but there are no gumballs
  32. No gumball dispensed