`
jgsj
  • 浏览: 960831 次
文章分类
社区版块
存档分类
最新评论

设计模式笔记10-状态模式

阅读更多

设计模式笔记10-状态模式


1 引言


基本常识:策略模式和状态模式是双胞胎,在出生时才分开。你已经知道了,策略模式是围绕可以互换的算法来创建成功业务的。然而,状态走的是更崇高的路,他通过改变对象内部的状态来帮助对象控制自己的行为。

2 正文


2.1 本章业务背景


本章的业务背景是一个糖果机,它有四种状态:没有25分钱、有25分钱、售出糖果、糖果售罄,可以对糖果机做出的操作有三种:投入25分钱、退回25分钱、转动曲柄、发放糖果,前三种是客户对机器做出的操作,最后一种是糖果机内部的操作。

2.2 以操作为对象的设计


现在我们以操作为对象,根据当前的状态命令糖果机作出下一步动作。
下面给出投入25分钱的方法代码:

	public void insertQuarter() {
		if (state == HAS_QUARTER) {
			System.out.println("You can't insert another quarter");
		} else if (state == NO_QUARTER) {
			state = HAS_QUARTER;
			System.out.println("You inserted a quarter");
		} else if (state == SOLD_OUT) {
			System.out.println("You can't insert a quarter, the machine is sold out");
		} else if (state == SOLD) {
        	System.out.println("Please wait, we're already giving you a gumball");
		}
	}

该来的躲不掉,变更请求。
增加需求:当曲柄被转动时,有10%的几率掉下来两颗糖果。

需求变更导致我们上述的设计暴露出一些弊端:
1、这份代码没有遵守开放-关闭的原则。
2、状态转换被埋藏在条件语句中,所以并不明显。
3、我们还没有把会改变的那部分包装起来。
4、未来加入的代码很有可能导致Bug。

2.3 新的设计


我们的计划是这样的:不要维护我们现有的代码,我们重写它以便于将状态对象封装在各自的类中,然后在动作发生时委托给当前状态。

我们在这里遵照我们的设计原则,所以最后应该得到一个容易维护的设计。我们要做的事情是:
1、首先,我们定义一个state接口。在这个接口内,糖果机的每个动作都有一个对象的方法。
2、然后为机器中的每个状态实现状态类。这些类将负责在对应的状态下进行机器的行为。
3、最后,我们要摆脱旧的条件代码,取而代之的方式是,将动作委托到状态类。

你将会看到,我们不仅遵守了设计原则,实际上我们还实现了状态模式。在重新完成代码之后我们再来了解状态模式的正式定义。

State接口

public interface State {
 
	public void insertQuarter();
	public void ejectQuarter();
	public void turnCrank();
	public void dispense();
}

实现我们的状态类

public class NoQuarterState implements State {
    GumballMachine gumballMachine;
 
    /*
     * 1 首先我们要实现State接口
     * 2 我们通过构造器得到糖果机的引用,然后记录在实例变量中
     * 3 以下分别是四种操作对应的处理方法
     */
    public NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
	public void insertQuarter() {
		System.out.println("You inserted a quarter");
		gumballMachine.setState(gumballMachine.getHasQuarterState());
	}
 
	public void ejectQuarter() {
		System.out.println("You haven't inserted a quarter");
	}
 
	public void turnCrank() {
		System.out.println("You turned, but there's no quarter");
	 }
 
	public void dispense() {
		System.out.println("You need to pay first");
	} 
 
	public String toString() {
		return "waiting for quarter";
	}
}

完整的糖果机类

public class GumballMachine {
	
	//所有的状态都在这里
	//以及实例变量
	State soldOutState;
	State noQuarterState;
	State hasQuarterState;
	State soldState;
	State winnerState;
 
	State state = soldOutState;
	int count = 0;
 
	//构造器取得糖果的初始数目并把它存放在一个实例变量中
	//每一种状态也都创建一个状态实例
	public GumballMachine(int numberGumballs) {
		soldOutState = new SoldOutState(this);
		noQuarterState = new NoQuarterState(this);
		hasQuarterState = new HasQuarterState(this);
		soldState = new SoldState(this);
		winnerState = new WinnerState(this);

		//如果超过0颗糖果,我们就把状态设为NoQuaterState
		this.count = numberGumballs;
 		if (numberGumballs > 0) {
			state = noQuarterState;
		} 
	}
 
	/*
	 * 1 现在这些动作变得容易实现了。我们只是委托到当前状态
	 * 2 请注意,我们不需要再GumballMachine中转杯一个dispense()的动作方法,
	 *   因为这是一个内部动作;用户不可以直接要求机器发放糖果。但我们再状态对象的
	 *   turnCrank()方法中调用dispense()方法的
	 */
	public void insertQuarter() {
		state.insertQuarter();
	}
 
	public void ejectQuarter() {
		state.ejectQuarter();
	}
 
	public void turnCrank() {
		state.turnCrank();
		state.dispense();
	}

	//这个方法 允许其他的对象(像我们的状态对象)将极其的状态转换到不同的状态
	void setState(State state) {
		this.state = state;
	}
 
	//这个极其提供了一个releaseBall()的辅助方法来释放出糖果,并将count实例变量的值减1
	void releaseBall() {
		System.out.println("A gumball comes rolling out the slot...");
		if (count != 0) {
			count = count - 1;
		}
	}
 
	//下面是一系列的get方法
	int getCount() {
		return count;
	}
 
	void refill(int count) {
		this.count = count;
		state = noQuarterState;
	}

    public State getState() {
        return state;
    }

    public State getSoldOutState() {
        return soldOutState;
    }

    public State getNoQuarterState() {
        return noQuarterState;
    }

    public State getHasQuarterState() {
        return hasQuarterState;
    }

    public State getSoldState() {
        return soldState;
    }

    public State getWinnerState() {
        return winnerState;
    }
 
	public String toString() {
		StringBuffer result = new StringBuffer();
		result.append("\nMighty Gumball, Inc.");
		result.append("\nJava-enabled Standing Gumball Model #2004");
		result.append("\nInventory: " + count + " gumball");
		if (count != 1) {
			result.append("s");
		}
		result.append("\n");
		result.append("Machine is " + state + "\n");
		return result.toString();
	}
}

2.4 定义状态模式


状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
这个描述中的第一部分附有相当多的涵义,是吧?因为这个模式将状态封装成为独立的类,并将动作委托到代表当前状态的对象,我们知道行为会随着内部状态而改变。糖果机提供了一个很好的例子:当糖果机是在不同状态时,你投入25分钱,就会得到不同的行为。
而这个定义的第二部分呢?一个对象“看起来好像修改了它的类”是什么意思呢?从客户的视角来看:如果说你使用的对象能够完全改变它的行为,那么你会觉得,这个对象实际上是从别的类实例化而来的。然后,实际上,你知道我们是在使用组合通过简单引用不同的状态对象来造成类改变的假象。

一般来说,我们把策略模式想成是除了继承之外的一种弹性替代方案。如果你使用继承定义了一个类的行为,你将被这个行为困住,甚至修改它很难。有了策略模式,你可以通过组合不同的对象改变行为。
我们把状态模式想成是不用在context中放置许多条件判断的替代方案。通过将行为包装进状态对象中,你可以通过在context内简单地改变状态对象来改变context的行为。

2.5 解决幸运糖果的问题


首先,我们增加一个状态,并且在转动曲柄是做一个随机数判断,根据结果决定是否额外多发放一个糖果。

我们为什么要增加一个新的状态?为什么不直接在SoldState中发放两颗糖果?
这是一个好问题。这两个状态几乎一样,唯一的差别在于,WinnerState状态会发放两颗糖果。你当然可以将发放两颗糖果的代码放在SoldState中,当然这么做也有缺点,因为你等于时将两个状态用一个状态类来代表。这样做你牺牲了状态类的清晰易懂来减少一些代码冗余。你也应该考虑到在前面的章节所学到的原则:一个类,一个责任。将WinnerState状态的责任放进SoldState中,你等于是让SoldState状态具有两个责任。那么促销方案结束后或者赢家的几率改变以后,你又该怎么办呢?所以,这必须用的只会来做折中。

3 本章总结

本章要点:
1、状态模式允许一个对象基于内部状态而拥有不同的行为
2、和程序状态机(PSM)不同,状态模式用类代表状态
3、Context会将行为委托给当前状态对象
4、通过将每个状态封装进一个类,我们把以后需要做的任何改变局部化了
5、状态模式和策略模式有相同的类图,但是它们意图不同
6、策略模式通常会用行为或算法来配置Context类
7、状态模式允许Context随着状态的改变而改变行为
8、状态转换可以由State类或Context类控制
9、使用状态模式通常会导致设计类的数目大量增加
10、状态类可以被多个Context实例共享

状态模式,根据字面意思其实很好理解。我们要处理的业务是一个流程,流程是由两部分组成:状态和动作,动作可以根据当前的状态(或者其他上下文)判断从而命令实例改变行为。缺陷管理流程大家应该都很熟悉,测试人员new一个bug report给测试经理,再由测试经理转给开发经理,开发经理根据模块分配给相关的开发人员,开发人员修改完成后转给组内人员进行校验,校验成功后转给开发经理,开发经理转给测试经理,测试经理转给测试人员,回归通过,问题单关闭。上述只列出了正常流程,每个状态都可以因为一些上下文原因将流程回退,比如测试经理发现bug report是个非问题,则将bug report走回,等等,这个流程相比本章的糖果机可能要复杂一点,但确实是一个很好的锻炼机会,有兴趣地朋友可以实现以下噢。







分享到:
评论

相关推荐

    尚硅谷设计模式源码笔记课件.zip

    行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式) 2) 学习目标:通过学习,学员...

    设计模式读书笔记

    设计模式1,包含了设计模式的基本介绍,以及状态机模式的读书笔记

    设计模式笔记(包含所有设计模式)个人总结提炼笔记

    3. 行为型模式:行为型模式关注对象之间的通信和协作,包括责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。这些模式可以帮助...

    设计模式经典样例笔记与代码Swift.zip

    设计模式经典样例笔记与代码Swift.zip 基础 [x] 类间的关系 [x] 设计原则 创建型 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定...

    JAVA23种设计模式及快捷记忆

    设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。工厂模式、抽象工厂模式、...

    java-design-patterns:Java 设计模式学习笔记

    Java Design PatternsJava 设计模式学习笔记,简单易懂,每个模式都有相应的代码示列,帮助学习理解。在线阅读地址:设计原则创建型模式作用:将创建与使用代码解耦结构型模式作用:将不同的功能代码解耦桥接模式...

    CEGUI深入解析

    CEGUI的详细笔记和文档,学习游戏编程的朋友一定要看看 第1章 CEGUI的简介 - 5 - 1.1CEGUI历史和本书使用的版本 - 5 - 1.2 CEGUI的编译和例子介绍 - 5 - 1.2.1CEGUI源代码的简介 - 5 - 1.2.2CEGUI源代码编译 - 7 - ...

    JS-design-pattern:根据曾探所著《JavaScript设计模式与开发实践》整理的学习笔记

    JS-design-pattern根据曾探所著《JavaScript设计模式与开发实践》整理的学习笔记基础知识创建模式结构模式行为模式状态模式策略模式发布订阅模式

    java源码解读-DesignPattern:Android源码设计模式解析与实战读书笔记源代码

    Android源码设计模式解析与实战读书笔记源代码 说明: 包名factorypattern.normal表示的是工厂方法模式的普通用法 包名factorypattern.practices表示的是工厂方法模式的常用 包名observerpattern表示的是观察者模式...

    asp.net知识库

    2分法-通用存储过程分页(top max模式)版本(性能相对之前的not in版本极大提高) 分页存储过程:排序反转分页法 优化后的通用分页存储过程 sql语句 一些Select检索高级用法 SQL server 2005中新增的排序函数及应用 ...

    Java学习笔记-个人整理的

    {10}反射}{141}{chapter.10} {10.1}Class}{141}{section.10.1} {10.1.1}Field}{145}{subsection.10.1.1} {10.1.2}Method}{145}{subsection.10.1.2} {10.1.3}Constructor}{145}{subsection.10.1.3} {10.2}其他...

    java-patterns:Java 23种基本的设计模式整料整理学习,责任链模式过滤器,工厂模式BeanFactory,观察者模式ContextListen等。结合Spring源码理解学习

    describe:设计模式学习笔记 逻辑结构图 代码结构图 设计模式简述 创建型模式,共五种:工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。 结构型模式,共七种:适配器模式,装饰器模式,代理模式,...

    轻松掌握state设计模式

    该PDF是我在学习state pattern时所做的笔记。里面包括了state pattern 的定义、何时使用、是否使用及实例四个部分,实例部分写的比较细,看完这个实例,相信大家也就知道怎么在自己的项目中应用state pattern了。这...

    左程云leetcode-hjLearningNotes:hj学习笔记

    第一部分:设计模式 & UML 简单工厂 工厂方法模式 抽象工厂模式 策略模式 责任链模式 命令模式 模板方法模式 适配器模式 代理模式 外观模式 组合模式 装饰模式 享元模式 桥接模式 Builder模式 状态模式 解释器模式 ...

    2009 达内Unix学习笔记

    (注 删除没有写权限的文件可以用 rm -f ,这是为了操作方便,是人性化的设计)。 x 执行权限;对目录,是进入该目录 - 表示没有权限 形式 - rw- r-- r-- 其中 第一个是文件类型(-表普通文件,d表目录,l表软...

    android源码java-Omni-Notes:Android的开源笔记记录应用程序

    素描笔记模式 主屏幕上的Notes快捷方式 导出/导入注释以备份 与Google即时集成:只需先说“写便笺”,然后再讲内容 多个小部件,DashClock扩展,Android 4.2锁屏兼容性 多国语言:支持30多种语言: 进一步的发展将...

    初级java笔试题-oopnotes:面向对象编程笔记本,设计原则和模式参考指南

    我已将此笔记本作为面向对象编程概念和设计模式的参考指南。 我的目标是让任何人都能找到遵循面向对象范式正确设计可重用且高效的代码所需的核心概念。 在尝试学习自己的同时,我一直在努力研究并浪费大量时间在多个...

    leetcode下载-LearningNotes:学习笔记

    状态模式 解释器模式 命令模式 备忘录模式 迭代器模式 模板方法模式 访问者模式 中介者模式 组合模式 装饰模式 享元模式 桥接模式 ##第二部分 ##第三部分 链表 字符串 树 图 查找 《剑指Offer》 《程序员面试金典》 ...

    LAN9514/LAN9514i中文数据手册.pdf

    • 支持全双工模式的集成10/100以太网MAC • 支持HP自动MDIX的集成10/100以太网PHY • 实现低功耗工作模式 • 最大限度地降低BOM成本 - 单个25 MHz晶振(无需为USB和以太网使用单独的 晶振,可节约成本) - 内置上电...

Global site tag (gtag.js) - Google Analytics