示例

一个类对外提供了多个行为,同时该类对象有多种状态,不同状态下对外的

行为的表现不同,我们该如何来设计该类让它对状态可以灵活扩展?

以自动售卖饮料机为例开发一个程序:

  1. 用户可以在饮料机上进行支付、退款、购买、取货操作
  2. 不同的状态下,这四种操作会有不同的表现

    例如:在用户没有支付的情况下,用户操作了退款、购买、取货会发生什么

简单例子

  1. public class DrinksSellingMachine {
  2. //未支付
  3. final static int NO_PAY = 0;
  4. //已支付
  5. final static int PAY = 1;
  6. //待取货
  7. final static int SOLD = 2;
  8. //已售罄
  9. final static int SOLD_OUT = 4;
  10. //当前状态
  11. private int state = SOLD_OUT;
  12. //当前库存
  13. private int store;
  14. public DrinksSellingMachine(int store) {
  15. this.store = store;
  16. if(this.store > 0) {
  17. this.state = NO_PAY;
  18. }
  19. }
  20. /** 支付 */
  21. public void pay() {
  22. switch (this.state) {
  23. case NO_PAY :
  24. System.out.println("支付成功,请确定购买饮料。");
  25. this.state = PAY;
  26. break;
  27. case PAY :
  28. System.out.println("已支付成功,请确定购买饮料。");
  29. break;
  30. case SOLD :
  31. System.out.println("待取饮料中,请稍后购买。");
  32. break;
  33. case SOLD_OUT :
  34. System.out.println("饮料已售罄,不可购买。");
  35. break;
  36. }
  37. }
  38. /** 退款 */
  39. public void refund() {
  40. switch (this.state) {
  41. case NO_PAY :
  42. System.out.println("尚未支付,请不要乱按。");
  43. break;
  44. case PAY :
  45. System.out.println("退款成功。");
  46. this.state = NO_PAY;
  47. break;
  48. case SOLD :
  49. System.out.println("已购买,请取用。");
  50. break;
  51. case SOLD_OUT :
  52. System.out.println("饮料已售罄,不可购买。");
  53. break;
  54. }
  55. }
  56. /** 购买 */
  57. public void buy() {
  58. switch (this.state) {
  59. case NO_PAY :
  60. System.out.println("尚未支付,请不要乱按。");
  61. break;
  62. case PAY :
  63. System.out.println("购买成功,请取用。");
  64. this.state = SOLD;
  65. break;
  66. case SOLD :
  67. System.out.println("已购买,请取用。");
  68. break;
  69. case SOLD_OUT :
  70. System.out.println("饮料已售罄,不可购买。");
  71. break;
  72. }
  73. }
  74. /** 取货 */
  75. public void getGoods() {
  76. switch (this.state) {
  77. case NO_PAY :
  78. System.out.println("尚未支付,请不要乱按。");
  79. break;
  80. case PAY :
  81. System.out.println("已购买,请取用。");
  82. case SOLD :
  83. System.out.println("正在出货中,请等待三秒。");
  84. this.store--;
  85. if (this.store == 0) {
  86. this.state = SOLD_OUT;
  87. } else {
  88. this.state = NO_PAY;
  89. }
  90. break;
  91. case SOLD_OUT :
  92. System.out.println("饮料已售罄,不可购买。");
  93. break;
  94. }
  95. }
  96. }

看到上面一大段代码,感觉很懵,如果现在需要扩展状态怎么办,需要对每一种操作下的方法进行修改,这将是一件麻烦的事情

如何能够灵活的扩展状态呢

四种操作是不会变的,但是状态是可以灵活变化的,下面改进代码:

改进代码

定义一个接口,每一种状态需要实现该接口:

  1. public interface State {
  2. /** 支付 */
  3. void pay();
  4. /** 退款 */
  5. void refund();
  6. /** 购买 */
  7. void buy();
  8. /** 取货 */
  9. void getGoods();
  10. }

未支付状态实现类:

  1. public class NoPayState implements State {
  2. private NewDrinksSellingMatchine matchine;
  3. public NoPayState(NewDrinksSellingMatchine matchine) {
  4. this.matchine = matchine;
  5. }
  6. @Override
  7. public void pay() {
  8. System.out.println("支付成功,请确定购买饮料。");
  9. this.matchine.state = this.matchine.PAY;
  10. }
  11. @Override
  12. public void refund() {
  13. System.out.println("尚未支付,请不要乱按。");
  14. }
  15. @Override
  16. public void buy() {
  17. System.out.println("尚未支付,请不要乱按。");
  18. }
  19. @Override
  20. public void getGoods() {
  21. System.out.println("尚未支付,请不要乱按。");
  22. }
  23. }

支付状态实现类:

  1. public class PayState implements State{
  2. private NewDrinksSellingMatchine matchine;
  3. public PayState(NewDrinksSellingMatchine matchine) {
  4. this.matchine = matchine;
  5. }
  6. @Override
  7. public void pay() {
  8. System.out.println("已支付成功,请确定购买饮料。");
  9. }
  10. @Override
  11. public void refund() {
  12. System.out.println("退款成功。");
  13. this.matchine.state = this.matchine.NO_PAY;
  14. }
  15. @Override
  16. public void buy() {
  17. System.out.println("已购买,请取用。");
  18. this.matchine.state = this.matchine.SOLD;
  19. }
  20. @Override
  21. public void getGoods() {
  22. System.out.println("请先确定购买。");
  23. }
  24. }

其他两种状态就不在这展示了,和上面两种差不多

  1. public class NewDrinksSellingMatchine {
  2. final State NO_PAY, PAY, SOLD, SOLD_OUT;
  3. State state;
  4. int store;
  5. public NewDrinksSellingMatchine(int store) {
  6. NO_PAY = new NoPayState(this);
  7. PAY = new PayState(this);
  8. SOLD = new SoldState(this);
  9. SOLD_OUT = new SoldOutState(this);
  10. this.store = store;
  11. if(this.store > 0) {
  12. this.state = NO_PAY;
  13. }
  14. }
  15. public void pay() {
  16. this.state.pay();
  17. }
  18. public void refund() {
  19. this.state.refund();
  20. }
  21. public void buy() {
  22. this.state.buy();
  23. }
  24. public void getGoods() {
  25. this.state.getGoods();
  26. }
  27. }

改进之后,如果需要对状态进行扩展,只需要实现State的接口就行了

状态模式

定义

一个类对外提供了多个行为,同时该类对象有多种状态,不同状态下对外的

行为的表现不同

意图

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

主要解决问题

对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为

何时使用

代码中包含大量与对象状态有关的条件语句

优缺点

优点:

  1. 封装了转换规则
  2. 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为
  3. 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块
  4. 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数

缺点:

  1. 状态模式的使用必然会增加系统类和对象的个数
  2. 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱
  3. 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码

类图:



涉及的角色:

  1. 抽象状态(State)角色:定义一个接口,用来封装环境(Context)对象的一个特定的状态所对应的行为
  2. 具体状态(ConcreteState)角色:每一个具体状态类都实现了环境(Context)的一个状态所对应的行为
  3. 环境(Context)角色:定义客户端所感兴趣的接口,并且保留一个具体状态类的实例,这个具体状态类的实例给出此环境对象的现有状态

对应的代码如下:

State接口:

  1. public interface State {
  2. void sampleOperation();
  3. }

ConcreteState类:

  1. public class ConcreteState implements State {
  2. @Override
  3. public void sampleOperation() {
  4. }
  5. }

Context类:

  1. public class Context {
  2. private State state;
  3. public void sampleOperation() {
  4. this.state.sampleOperation();
  5. }
  6. public void setState(State state) {
  7. this.state = state;
  8. }
  9. }

曾侯乙编钟

曾侯乙编钟,1979年在湖北出土,一套共65件,总音域跨5个八度,12个半音齐全,每一只钟都发出不同的音

下面以曾侯乙编钟为例,熟悉一下状态模式:



每一只钟都需要实现的接口:

  1. public interface ClockState {
  2. /** 打击钟 */
  3. void blow();
  4. void otherClock();
  5. }

具体每一只钟的实现:

  1. public class ClockConcreteStateC implements ClockState {
  2. @Override
  3. public void blow() {
  4. System.out.println("钟C被打击");
  5. }
  6. @Override
  7. public void otherClock() {
  8. System.out.println("钟A B没有被打击");
  9. }
  10. }
  1. public class ClockConcreteStateB implements ClockState {
  2. @Override
  3. public void blow() {
  4. System.out.println("钟B被打击");
  5. }
  6. @Override
  7. public void otherClock() {
  8. System.out.println("钟A C没有被打击");
  9. }
  10. }
  1. public class ClockConcreteStateA implements ClockState {
  2. @Override
  3. public void blow() {
  4. System.out.println("钟A被打击");
  5. }
  6. @Override
  7. public void otherClock() {
  8. System.out.println("钟B C没有被打击");
  9. }
  10. }

曾候乙编钟,乐师选择一个钟打击它,每一只钟的每个音代表一个状态:

  1. public class ClockContext {
  2. private ClockState state;
  3. public void blow() {
  4. this.state.blow();
  5. this.state.otherClock();
  6. }
  7. public void setState(ClockState state) {
  8. this.state = state;
  9. }
  10. }

测试类:

  1. public class Test {
  2. public static void main(String[] args) {
  3. ClockContext context = new ClockContext();
  4. context.setState(new ClockConcreteStateA());
  5. context.blow();
  6. }
  7. }



类图:



注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个

状态模式-命令模式-策略模式

策略模式:侧重的是一个行为的多个算法实现,可互换算法,比如优惠活动满减和打折都是算法,可以选择其中一个来买买买

命令模式:侧重的是为多个行为提供灵活的执行方式,比如上面的奶茶,每个顾客购买奶茶的命令都是一个行为,而不同的奶茶制作方式不一样,则就需要灵活的去制作奶茶

状态模式:应用于状态机的情况

设计原则:

  • 区分变与不变,隔离变化
  • 面向接口编程
  • 多用组合,少用继承

曾侯乙编钟引发的遐想之Java设计模式:状态模式的更多相关文章

  1. JAVA 设计模式 状态模式

    用途 状态模式 (State) 当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类. 状态模式是一种行为型模式. 结构

  2. java设计模式---状态模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述状态(State)模式的: 状态模式,又称状态对象模式(Pattern of Objects for States),状态模式是对象的行为 ...

  3. Java设计模式—状态模式

    状态模式又是一个比较难的设计模式 定义如下: 当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类. 个人理解:通俗的讲,状态模式就是状态的改变引起了行为的改变,但是,我们只能看到行为的 ...

  4. Java设计模式-状态模式(State)

    核心思想就是:当对象的状态改变时,同时改变其行为,很好理解!就拿QQ来说,有几种状态,在线.隐身.忙碌等,每个状态对应不同的操作,而且你的好友也能看到你的状态,所以,状态模式就两点:1.可以通过改变状 ...

  5. 14. 星际争霸之php设计模式--状态模式

    题记==============================================================================本php设计模式专辑来源于博客(jymo ...

  6. Java设计模式——组合模式

    JAVA 设计模式 组合模式 用途 组合模式 (Component) 将对象组合成树形结构以表示“部分-整体”的层次结构.组合模式使得用户对单个对象和组合对象的使用具有唯一性. 组合模式是一种结构型模 ...

  7. java设计模式--单列模式

    java设计模式--单列模式 单列模式定义:确保一个类只有一个实例,并提供一个全局访问点. 下面是几种实现单列模式的Demo,每个Demo都有自己的优缺点: Demo1: /** * 单列模式需要满足 ...

  8. 3.java设计模式-建造者模式

    Java设计模式-建造者模式 在<JAVA与模式>一书中开头是这样描述建造(Builder)模式的: 建造模式是对象的创建模式.建造模式可以将一个产品的内部表象(internal repr ...

  9. Java设计模式-代理模式之动态代理(附源代码分析)

    Java设计模式-代理模式之动态代理(附源代码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的差别就是:动态代理是在执行时刻动态的创建出代理类及其对象. 上篇中的静态代 ...

随机推荐

  1. JUnit5学习之三:Assertions类

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  2. Project facet Java version 1.7 is not supported.解决方法

    最近遇到这个问题,在网上查到的解决方案基本都是下面几个: 1.右击项目,properties,project facets,改动java的version为1.7. 2.window,propertie ...

  3. js 表格插入指定行

    js在table指定tr行上或下面添加tr行 function onAddTR(trIndex)         {             var tb = document.getElementB ...

  4. SpringBoot接收map类型的参数

    如果某个controller的某个接口的参数特别多,也可以使用map的方式来接收参数,接收之后使用get方法获取即可. 1)get请求方式,定义map接收方式 @RequestParam(requir ...

  5. 第七届蓝桥杯JavaB组——第7题剪邮票

    题目: 剪邮票 如[图1.jpg], 有12张连在一起的12生肖的邮票. 现在你要从中剪下5张来,要求必须是连着的. (仅仅连接一个角不算相连) 比如,[图2.jpg],[图3.jpg]中,粉红色所示 ...

  6. 1.3.1 apache的配置(下)

    (1)httpd.conf的配置 使用文本编辑工具(推荐使用Editplus.UltraEdit等工具),打开httpd.conf. 其中,行首为#的部分为注释部分,不会被apache服务器程序进行读 ...

  7. SSAS表格模型

    Analysis Services 是在决策支持和业务分析中使用的分析数据引擎 (Vertipaq) . 它为商业智能提供企业级语义数据模型功能 (BI) .数据分析和报告应用程序,如 Power B ...

  8. Java I/O流 05

    I/O流·文件递归 统计该文件夹的大小 * 需求:从键盘就收一个文件夹路径,统计该文件夹的大小 package com.heima.test; import java.io.File; import ...

  9. AtCoder Beginner Contest 194

    A I Scream int main() { IOS; int a, b; cin >> a >> b; if(a + b >= 15 && b > ...

  10. VUE中的子父组件、兄弟组件之间相互传值,相互调用彼此的方法

    vue--组件传值 父组件传值给子组件--"props" 一.父组件--示例 <template> <child :choose-data="choos ...