示例

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

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

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

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

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

简单例子

public class DrinksSellingMachine {

    //未支付
final static int NO_PAY = 0;
//已支付
final static int PAY = 1;
//待取货
final static int SOLD = 2;
//已售罄
final static int SOLD_OUT = 4; //当前状态
private int state = SOLD_OUT;
//当前库存
private int store; public DrinksSellingMachine(int store) {
this.store = store;
if(this.store > 0) {
this.state = NO_PAY;
}
} /** 支付 */
public void pay() {
switch (this.state) {
case NO_PAY :
System.out.println("支付成功,请确定购买饮料。");
this.state = PAY;
break;
case PAY :
System.out.println("已支付成功,请确定购买饮料。");
break;
case SOLD :
System.out.println("待取饮料中,请稍后购买。");
break;
case SOLD_OUT :
System.out.println("饮料已售罄,不可购买。");
break;
} } /** 退款 */
public void refund() {
switch (this.state) {
case NO_PAY :
System.out.println("尚未支付,请不要乱按。");
break;
case PAY :
System.out.println("退款成功。");
this.state = NO_PAY;
break;
case SOLD :
System.out.println("已购买,请取用。");
break;
case SOLD_OUT :
System.out.println("饮料已售罄,不可购买。");
break;
}
} /** 购买 */
public void buy() {
switch (this.state) {
case NO_PAY :
System.out.println("尚未支付,请不要乱按。");
break;
case PAY :
System.out.println("购买成功,请取用。");
this.state = SOLD;
break;
case SOLD :
System.out.println("已购买,请取用。");
break;
case SOLD_OUT :
System.out.println("饮料已售罄,不可购买。");
break;
}
} /** 取货 */
public void getGoods() {
switch (this.state) {
case NO_PAY :
System.out.println("尚未支付,请不要乱按。");
break;
case PAY :
System.out.println("已购买,请取用。");
case SOLD :
System.out.println("正在出货中,请等待三秒。");
this.store--;
if (this.store == 0) {
this.state = SOLD_OUT;
} else {
this.state = NO_PAY;
}
break;
case SOLD_OUT :
System.out.println("饮料已售罄,不可购买。");
break;
}
}
}

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

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

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

改进代码

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

public interface State {

    /** 支付 */
void pay(); /** 退款 */
void refund(); /** 购买 */
void buy(); /** 取货 */
void getGoods();
}

未支付状态实现类:

public class NoPayState implements State {

    private NewDrinksSellingMatchine matchine;

    public NoPayState(NewDrinksSellingMatchine matchine) {
this.matchine = matchine;
} @Override
public void pay() {
System.out.println("支付成功,请确定购买饮料。");
this.matchine.state = this.matchine.PAY;
} @Override
public void refund() {
System.out.println("尚未支付,请不要乱按。");
} @Override
public void buy() {
System.out.println("尚未支付,请不要乱按。");
} @Override
public void getGoods() {
System.out.println("尚未支付,请不要乱按。");
}
}

支付状态实现类:

public class PayState implements State{

    private NewDrinksSellingMatchine matchine;

    public PayState(NewDrinksSellingMatchine matchine) {
this.matchine = matchine;
} @Override
public void pay() {
System.out.println("已支付成功,请确定购买饮料。");
} @Override
public void refund() {
System.out.println("退款成功。");
this.matchine.state = this.matchine.NO_PAY;
} @Override
public void buy() {
System.out.println("已购买,请取用。");
this.matchine.state = this.matchine.SOLD;
} @Override
public void getGoods() {
System.out.println("请先确定购买。");
}
}

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

public class NewDrinksSellingMatchine {

    final State NO_PAY, PAY, SOLD, SOLD_OUT;

    State state;

    int store;

    public NewDrinksSellingMatchine(int store) {
NO_PAY = new NoPayState(this);
PAY = new PayState(this);
SOLD = new SoldState(this);
SOLD_OUT = new SoldOutState(this);
this.store = store;
if(this.store > 0) {
this.state = NO_PAY;
}
} public void pay() {
this.state.pay();
} public void refund() {
this.state.refund();
} public void buy() {
this.state.buy();
} public void getGoods() {
this.state.getGoods();
}
}

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

状态模式

定义

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

行为的表现不同

意图

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

主要解决问题

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

何时使用

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

优缺点

优点:

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

缺点:

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

类图:



涉及的角色:

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

对应的代码如下:

State接口:

public interface State {

    void sampleOperation();
}

ConcreteState类:

public class ConcreteState implements State {
@Override
public void sampleOperation() { }
}

Context类:

public class Context {

    private State state;

    public void sampleOperation() {
this.state.sampleOperation();
} public void setState(State state) {
this.state = state;
}
}

曾侯乙编钟

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

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



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

public interface ClockState {

    /** 打击钟 */
void blow(); void otherClock();
}

具体每一只钟的实现:

public class ClockConcreteStateC implements ClockState {
@Override
public void blow() {
System.out.println("钟C被打击");
} @Override
public void otherClock() {
System.out.println("钟A B没有被打击");
}
}
public class ClockConcreteStateB implements ClockState {
@Override
public void blow() {
System.out.println("钟B被打击");
} @Override
public void otherClock() {
System.out.println("钟A C没有被打击");
}
}
public class ClockConcreteStateA implements ClockState {
@Override
public void blow() {
System.out.println("钟A被打击");
} @Override
public void otherClock() {
System.out.println("钟B C没有被打击");
}
}

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

public class ClockContext {

    private ClockState state;

    public void blow() {
this.state.blow();
this.state.otherClock();
} public void setState(ClockState state) {
this.state = state;
}
}

测试类:

public class Test {
public static void main(String[] args) {
ClockContext context = new ClockContext();
context.setState(new ClockConcreteStateA());
context.blow();
}
}



类图:



注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 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. java自学第4期——:Scanner类、匿名对象介绍、Random类、ArrayList集合、标准类格式、String类、static静态、Arrays工具类、Math类(1)

    一.Scanner类 1.api简介: 应用程序编程接口 2.Scanner类: 作用:获取键盘输入的数据 位置: java.util.Scanner. 使用:使用成员方法nextInt() 和 ne ...

  2. 微信小程序:添加全局的正在加载中图标效果

    在发送请求的时候,显示一个正在加载中的小图标.在加载下一页的时候也显示正在加载中.同时数据请求回来了,把加载中进行关闭. 开发----API-----界面 在哪里添加这两段代码会比较方便呢?一个项目有 ...

  3. 第46天学习打卡(四大函数式接口 Stream流式计算 ForkJoin 异步回调 JMM Volatile)

    小结与扩展 池的最大的大小如何去设置! 了解:IO密集型,CPU密集型:(调优)  //1.CPU密集型 几核就是几个线程 可以保持效率最高 //2.IO密集型判断你的程序中十分耗IO的线程,只要大于 ...

  4. Django3.0 + nginx + uwsgi 部署

    CentOS7.6 下部署Django3.0应用,使用nginx+uwsgi部署: 1. uwsgi部署 pip install uwsgi 在项目的根目录中,新建文件夹 conf, 然后进入conf ...

  5. 防数据泄露_MySQL库和数据安全

    目录 攻击场景 外部入侵 内部盗取 防御体系建设 参考 在企业安全建设中有一个方向是防数据泄露,其中一块工作就是保障数据库安全,毕竟这里是数据的源头.当然数据库也分不同的种类,不同类型的数据库的防护手 ...

  6. #progma pack(x)说明

    1.字节对齐(内存相关) 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数 ...

  7. Celery:进一步探索

    一.创建Celery专用模块 对于大型项目,一般需要创建一个专用模块,便于管理. 1.1 模块结构 proj/__init__.py /celery.py /tasks.py proj/celery. ...

  8. FreeBSD——艺术、科学、哲学概论

    FreeBSD--艺术.科学.哲学概论→→→→→概论: 信息都有一定的时效性.那么现在是什么时间?现在已经发布了 FreeBSD 12.2,距离下一个版本 13.0 还有约 4 个月. 『约定』 使用 ...

  9. 字符串匹配-BF算法和KMP算法

    声明:图片及内容基于https://www.bilibili.com/video/av95949609 BF算法 原理分析 Brute Force 暴力算法 用来在主串中查找模式串是否存以及出现位置 ...

  10. P2516 [HAOI2010]最长公共子序列 题解(LCS)

    题目链接 最长公共子序列 解题思路 第一思路: 1.用\(length[i][j]\)表示\(a\)串的前\(i\)个字符与\(b\)串的前\(j\)个字符重叠的最长子串长度 2.用\(num[i][ ...