示例

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

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

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

  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. window.onresize绑定事件以及解绑事件

    问题描述 在Vue工程中,添加样式,部分需要做到自适应,需要添加resize事件,由于是单页面应用,如果组件初始化的时候绑定事件,在切换页面的时候不去注销事件,如果来回切换,会让resize事件执行多 ...

  2. JavaWeb实现用户登录注册功能实例代码(基于Servlet+JSP+JavaBean模式)

    一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp ...

  3. 报错问题: AtrributeError:module ‘allure’ has no attribute ‘’severity_level’

    问题:执行命令报错:pytest -s -q --alluredir report 报错问题: AtrributeError:module 'allure' has no attribute ''se ...

  4. 八. SpringCloud消息总线

    1. 消息总线概述 1.1 分布式配置的动态刷新问题 Linux运维修改Github上的配置文件内容做调整 刷新3344,发现ConfigServer配置中心立刻响应 刷新3355,发现ConfigC ...

  5. Java的特性和优势以及不同版本的分类,jdk,jre,jvm的联系与区别,javadoc的生成

    Java 1.Java的特性和优势 Write Once,Run Anywhere 简单性 面向对象 可移植性 高性能 分布式 动态性 多线程 安全性 健壮性 2.Java的三大版本 JavaSE:标 ...

  6. BuaacodingT141 microhhh的回城 题解(模拟)

    题目链接 microhhh的回城 解题思路 这题挺有意思的.本来寻思放在\(DS\)第一次练习赛应该不会很难吧,结果愣是卡在数据范围上写不出来. 然后暴力过掉了,但是用了\(1019ms\).感觉可以 ...

  7. 2017算法期末复习练习赛-G Beihang Couple Pairing Comunity 2017 题解(网络流)

    理解不够透彻.好题不可浪费,写题解以增进理解.会陆续补充题目.(咕咕咕) G Beihang Couple Pairing Comunity 2017 题目链接 Beihang Couple Pair ...

  8. Webpack 学习笔记(1) 开始

    目录 参考资料 1. 基础设定 2. 创建一个包 3. 使用配置文件完成打包命令 4. 使用 NPM Scripts 完成打包命令 参考资料 Getting Started | Webpack web ...

  9. 颠覆你认知的Python3.9

    我通读了python 3.9发行说明和相关的讨论.根据这些信息,我想写一个全面的指南,以便每个人都能一眼了解这些功能及其详细的工作原理 原文地址,点击这里,观看效果更佳 简而言之 从字典更新/合并到添 ...

  10. Tex中的引号(JAVA语言)

    package 第三章; import java.util.Scanner; public class Tex中的引号 { public static void main(String[] args) ...