GOF23种设计模式在Java中的应用(part 3)

本文接续:GOF23种设计模式在Java中的应用(part 2)

行为型模式

行为型模式关注系统中对象之间的相互交互.,研究系统在运行时对象之间的相互通信和协作,进一步明确对象的职责,共有11种模式。
创建型模式关注对象的创建过程。
结构型模式关注对象和类的组织。

行为型模式汇总:

①责任链模式     chain of responsibility pattern
②命令模式        command   pattern
③解释器模式    ínterpreter  pattern
④迭代器模式      íterator   pattern
⑤中介者模式     mediator  pattern
⑥备忘录模式    memento  pattern
⑦观察者模式    observer  pattern
⑧状态模式      state   pattern
⑨策略模式      strategy  pattern
⑩模板方法模式  template method  pattern
⑪访问者模式    vísítor  pattern

目录

2.14 责任链模式

2.15 迭代器模式

2.16 中介者模式

2.17 命令模式

2.18 解释器模式

2.19 访问者模式

2.20 策略模式

2.21 模板方法

2.22 状态模式

2.23 观察者模式

2.24 备忘录模式


2.14 责任链模式

2.14.1 责任链模式定义:

将能够处理同一类请求对象连成一条链,所提交的请求沿着链传递,链上的对象逐个判断是否有能力处理该请求,如果能则处理,如果不能则传递给链上的下一个对象。

责任链模式通用类图

责任链模式的核心在“链”上,“链”是由多个处理者ConcreteHandler组成的。

 2.14.2 责任链模式的优缺点

优点:

责任链模式非常显著的优点是将请求和处理分开。请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌(例如在J2EE项目开发中,可以剥离出无状态Bean由责任链处理),两者解耦,提高系统的灵活性。

缺点:

责任链有两个非常显著的缺点:一是性能问题,每个请求都是从链头遍历到链尾,特别是在链比较长的时候,性能是一个非常大的问题。二是调试不很方便,特别是链条比较长,环节比较多的时候,由于采用了类似递归的方式,调试的时候逻辑可能比较复杂。

责任链模式的注意事项

链中节点数量需要控制,避免出现超长链的情况,一般的做法是在Handler中设置一个最大节点数量,在setNext方法中判断是否已经是超过其阈值,超过则不允许该链建立,避免无意识地破坏系统性能。

场景:
-打牌时,轮流出牌
-接力赛跑
-大学中,奖学金审批
-公司中,公文审批

 

场景:
– 公司里面,报销个单据需要经过流程:
• 申请人填单申请,申请给经理
• 小于1000,经理审查。
• 超过1000,交给总经理审批。
• 总经理审批通过
– 公司里面,请假条的审批过程:
• 如果请假天数小于3天,主任审批
• 如果请假天数大于等于3天,小于10天,经理审批
• 如果大于等于10天,小于30天,总经理审批
• 如果大于等于30天,提示拒绝

场景:
– 公司里面,SCM(Supply Chain Management供应链管理)系统中,
采购审批子系统的设计:
• 采购金额小于5万,主任审批
• 采购金额大于等于5万,小于10万,经理审批
• 采购金额大于等于10万,小于20万,副总经理审批
• 采购金额大于等于20万,总经理审批

2.14.3 实例演示:

请假条审批流程案例

package cn.xiaomifeng1010.chainOfResp;

/**
 * 封装请假的基本信息
 * @author Administrator
 *
 */
public class LeaveRequest {
	private String empName;
	private int leaveDays;
	private String reason;
	
	
	public LeaveRequest(String empName, int leaveDays, String reason) {
		super();
		this.empName = empName;
		this.leaveDays = leaveDays;
		this.reason = reason;
	}
	public String getEmpName() {
		return empName;
	}
	public void setEmpName(String empName) {
		this.empName = empName;
	}
	public int getLeaveDays() {
		return leaveDays;
	}
	public void setLeaveDays(int leaveDays) {
		this.leaveDays = leaveDays;
	}
	public String getReason() {
		return reason;
	}
	public void setReason(String reason) {
		this.reason = reason;
	} 
	
	
}
package cn.xiaomifeng1010.chainOfResp;

/**
 * 抽象类
 * @author Administrator
 *
 */
public abstract class Leader {
	protected String name;
	protected Leader nextLeader; //责任链上的后继对象
	
	public Leader(String name) {
		super();
		this.name = name;
	}
	
	//设定责任链上的后继对象
	public void setNextLeader(Leader nextLeader) {
		this.nextLeader = nextLeader;
	}
	
	
	/**
	 * 处理请求的核心的业务方法
	 * @param request
	 */
	public abstract void handleRequest(LeaveRequest request);
	
	
	
}
package cn.xiaomifeng1010.chainOfResp;

/**
 * 主任
 * @author Administrator
 *
 */
public class Director extends Leader {

	public Director(String name) {
		super(name);
	}

	@Override
	public void handleRequest(LeaveRequest request) {
		if(request.getLeaveDays()<3){
			System.out.println("员工:"+request.getEmpName()+"请假,天数:"+request.getLeaveDays()+",理由:"+request.getReason());
			System.out.println("主任:"+this.name+",审批通过!");
		}else{
			if(this.nextLeader!=null){
				this.nextLeader.handleRequest(request);
			}
		}
	}

}
package cn.xiaomifeng1010.chainOfResp;

/**
 * 经理
 * @author Administrator
 *
 */
public class Manager extends Leader {

	public Manager(String name) {
		super(name);
	}

	@Override
	public void handleRequest(LeaveRequest request) {
		if(request.getLeaveDays()<10){
			System.out.println("员工:"+request.getEmpName()+"请假,天数:"+request.getLeaveDays()+",理由:"+request.getReason());
			System.out.println("经理:"+this.name+",审批通过!");
		}else{
			if(this.nextLeader!=null){
				this.nextLeader.handleRequest(request);
			}
		}
	}

}
package cn.xiaomifeng1010.chainOfResp;

/**
 * 副总经理
 * @author Administrator
 *
 */
public class ViceGeneralManager extends Leader {

	public ViceGeneralManager(String name) {
		super(name);
	}

	@Override
	public void handleRequest(LeaveRequest request) {
		if(request.getLeaveDays()<20){
			System.out.println("员工:"+request.getEmpName()+"请假,天数:"+request.getLeaveDays()+",理由:"+request.getReason());
			System.out.println("副总经理:"+this.name+",审批通过!");
		}else{
			if(this.nextLeader!=null){
				this.nextLeader.handleRequest(request);
			}
		}
	}

}
package cn.xiaomifeng1010.chainOfResp;

/**
 * 总经理
 * @author Administrator
 *
 */
public class GeneralManager extends Leader {

	public GeneralManager(String name) {
		super(name);
	}

	@Override
	public void handleRequest(LeaveRequest request) {
		if(request.getLeaveDays()<30){
			System.out.println("员工:"+request.getEmpName()+"请假,天数:"+request.getLeaveDays()+",理由:"+request.getReason());
			System.out.println("总经理:"+this.name+",审批通过!");
		}else{
			System.out.println("莫非"+request.getEmpName()+"想辞职,居然请假"+request.getLeaveDays()+"天!");
		}
	}

}

 client客户端

package cn.xiaomifeng1010.chainOfResp;

public class Client {
	public static void main(String[] args) {
		Leader a = new Director("张三");
		Leader b = new Manager("李四");
		Leader b2 = new ViceGeneralManager("李小四");
		Leader c = new GeneralManager("王五");
		//组织责任链对象的关系
		a.setNextLeader(b);
		b.setNextLeader(b2);
		b2.setNextLeader(c);
		
		//开始请假操作
		LeaveRequest req1 = new LeaveRequest("TOM", 15, "回英国老家探亲!");
		a.handleRequest(req1);
		
	}
}

关系类图:

上面实例的解析:

添加新的处理对象:
– 由于责任链的创建完全在客户端,因此新增新的具体处理者对原有类
库没有任何影响,只需添加新的类,然后在客户端调用时添加即可。
符合开闭原则。
– 案例:
• 我们可以在请假处理流程中,增加新的“副总经理”角色,审批大于等于
10天,小于20天的请假。审批流程变为:
① 如果请假天数小于3天,主任审批
② 如果请假天数大于等于3天,小于10天,经理审批
③ 大于等于10天,小于20天的请假,副总经理审批
④ 如果大于等于20天,小于30天,总经理审批
⑤ 如果大于等于30天,提示拒绝

 链表方式定义职责链(上一个案例)
• 非链表方式实现职责链
– 通过集合、数组生成职责链更加实用!实际上,很多项目中,每个具
体的Handler并不是由开发团队定义的,而是项目上线后由外部单位追
加的,所以使用链表方式定义COR链就很困难。

2.14.4 开发中常见的场景:
– Java中,异常机制就是一种责任链模式。一个try可以对应多个catch,当第一个catch不匹配类型,则自动跳到第二个catch.
– Javascript语言中,事件的冒泡和捕获机制。Java语言中,事件的处理采用观察者模式。
– Servlet开发中,过滤器的链式处理
– Struts2中,拦截器的调用也是典型的责任链模式

 

2.15 迭代器模式

2.15.1 迭代器模式定义:

它提供一种方法访问一个容器对象中各个元素,而又不需要暴露该对象的内部细节。

提供一种可以遍历聚合对象的方式。又称为:游标cursor模式
– 聚合对象:存储数据
– 迭代器:遍历数据

                                                             迭代器模式的通用类图 

Iterator抽象迭代器

抽象迭代器负责定义访问和遍历元素的接口,而且基本上是有固定的3个方法:first()获得第一个元素,next()访问下一个元素,isDone()是否已经访问到底部(java叫做hasNext()方法)。

ConcreteIterator具体迭代器

具体迭代器角色实现迭代器接口,完成容器元素的遍历。

Aggregate抽象容器

容器角色负责提供创建具体迭代器角色的接口,必然提供一个类似createIterator()这样的方法,在java中一般是iterator()方法。

Concrete Aggregate具体容器

具体容器实现容器接口定义的方法,创建出容纳迭代器的对象。

基本案例:
– 实现正向遍历的迭代器
– 实现逆向遍历的迭代器
• 开发中常见的场景:
– JDK内置的迭代器(List/Set)

2.15.2 迭代器模式实例演示

package cn.xiaomifeng1010.iterator;


/**
 *  自定义的迭代器接口
 * @author Administrator
 *
 */
public interface MyIterator {
	void first();	//将游标指向第一个元素
	void next();	//将游标指向下一个元素
	boolean hasNext();//判断是否存在下一个元素
	
	boolean isFirst();
	boolean isLast();
	
	Object getCurrentObj();  //获取当前游标指向的对象
}
package cn.xiaomifeng1010.iterator;

import java.util.ArrayList;
import java.util.List;

/**
 * 自定义的聚合类
 * @author Administrator
 *
 */
public class ConcreteMyAggregate {
	private List<Object> list = new ArrayList<Object>();

	public void addObject(Object obj){
		this.list.add(obj);
	}
	public void removeObject(Object obj){
		this.list.remove(obj);
	}

	public List<Object> getList() {
		return list;
	}

	public void setList(List<Object> list) {
		this.list = list;
	}
	
	
	//获得迭代器
	public MyIterator  createIterator(){
		return new ConcreteIterator();
	}
	
	
	
	//使用内部类定义迭代器,可以直接使用外部类的属性
	private class ConcreteIterator implements MyIterator {

		private int cursor;  //定义游标用于记录遍历时的位置
		
		@Override
		public void first() {
			cursor = 0;
		}

		@Override
		public Object getCurrentObj() {
			return list.get(cursor);
		}

		@Override
		public boolean hasNext() {
			if(cursor<list.size()){
				return true;
			}
			return false;
		}

		@Override
		public boolean isFirst() {
			return cursor==0?true:false;
		}

		@Override
		public boolean isLast() {
			return cursor==(list.size()-1)?true:false;
		}

		@Override
		public void next() {
			if(cursor<list.size()){
				cursor++;
			}
		}
		
	}
	
	
	
	
}
package cn.xiaomifeng1010.iterator;

public class Client {
	
	public static void main(String[] args) {
		ConcreteMyAggregate cma = new ConcreteMyAggregate();
		cma.addObject("aa");
		cma.addObject("bb");
		cma.addObject("cc");
		
		MyIterator iter = cma.createIterator();
		while(iter.hasNext()){
			System.out.println(iter.getCurrentObj());
			iter.next();
		}
		
	}
}

2.16 中介者模式

2.16.1 中介者模式定义:

用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

Mediator ------抽象中介者角色

抽象中介者定义统一的接口,用于各同事角色之间的通信。

Concrete Mediator ----具体中介者角色

具体中介者角色通过协调各同事角色实现协作行为,因此它必须依赖于各个同事角色。

Colleague -----同事角色

每一个同事角色都知道中介者角色,而且与其他的同事角色通信的时候,一定要通过中介者角色协作。每个同事类的行为分为两种:一种是同事本身的行为,比如改变对象本身的状态,处理自己的行为等,这种行为叫做自发行为(Self--Method),与其他的同事类或中介者没有任何的依赖:第二种是必须依赖中介者才能完成的行为,叫做依赖方法(Dep--Method).

2.16.2 中介者模式实例演示

package cn.xiaomifeng1010.mediator;

public interface Mediator {
	
	void register(String dname,Department d);
	
	void command(String dname);
	
}
package cn.xiaomifeng1010.mediator;

import java.util.HashMap;
import java.util.Map;

public class President implements Mediator {
	
	private Map<String,Department> map = new HashMap<String , Department>();
	
	@Override
	public void command(String dname) {
		map.get(dname).selfAction();
	}

	@Override
	public void register(String dname, Department d) {
		map.put(dname, d);
	}

}

部门接口:

package cn.xiaomifeng1010.mediator;

//同事类的接口
public interface Department {
	void selfAction(); //做本部门的事情
	void outAction();  //向总经理发出申请
}
package cn.xiaomifeng1010.mediator;

public class Development implements Department {

	private Mediator m;  //持有中介者(总经理)的引用
	
	public Development(Mediator m) {
		super();
		this.m = m;
		m.register("development", this);
	}

	@Override
	public void outAction() {
		System.out.println("汇报工作!没钱了,需要资金支持!");
	}

	@Override
	public void selfAction() {
		System.out.println("专心科研,开发项目!");
	}

}
package cn.xiaomifeng1010.mediator;

public class Finacial implements Department {

	private Mediator m;  //持有中介者(总经理)的引用
	
	public Finacial(Mediator m) {
		super();
		this.m = m;
		m.register("finacial", this);
	}

	@Override
	public void outAction() {
		System.out.println("汇报工作!没钱了,钱太多了!怎么花?");
	}

	@Override
	public void selfAction() {
		System.out.println("数钱!");
	}

}
package cn.xiaomifeng1010.mediator;

public class Market implements Department {

	private Mediator m;  //持有中介者(总经理)的引用
	
	public Market(Mediator m) {
		super();
		this.m = m;
		m.register("market", this);
	}

	@Override
	public void outAction() {
		System.out.println("汇报工作!项目承接的进度,需要资金支持!");
		
		m.command("finacial");
		
	}

	@Override
	public void selfAction() {
		System.out.println("跑去接项目!");
	}

}

client客户端

package cn.xiaomifeng1010.mediator;

public class Client {
	public static void main(String[] args) {
		Mediator m = new President();
		
		Market   market = new Market(m);
		Development devp = new Development(m);
		Finacial f = new Finacial(m);
		
		market.selfAction();
		market.outAction();
		
	}
}

关系类图:

 中介者模式的本质:
– 解耦多个同事对象之间的交互关系。每个对象都持有中介者对象的引用,只跟中介者对象打交道。我们通过中介者对象统一管理这些交互关系
• 开发中常见的场景:
– MVC模式(其中的C,控制器就是一个中介者对象。M和V都和他打交道)
– 窗口游戏程序,窗口软件开发中窗口对象也是一个中介者对象
– 图形界面开发GUI中,多个组件之间的交互,可以通过引入一个中介者
对象来解决,可以是整体的窗口对象或者DOM对象
– Java.lang.reflect.Method#invoke()

2.16.3 中介者模式的优缺点

优点:

中介者模式的优点就是减少类间的依赖,把原有的一对多的依赖编程了一对一的依赖,同事类只依赖中介者,减少了依赖,当然同时也降低了类间的耦合。

缺点:

中介者模式的缺点就是中介者会膨胀得很大,而且逻辑复杂,原本N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂。

2.17 命令模式

2.17.1 命令模式的定义:

把一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。也称之为:动作Action模式、事务transaction模式。

                                                   命令模式通用类图

Receive-----接受者角色

该角色就是干活的角色,命令传递到这里应该被执行的,具体到上面的三个实现类:

Command---命令角色

需要执行的所有命令都在这里声明。

Invoker-----调用者角色

接收到命令,并执行命令。

 

开发中常见的场景:
– Struts2中,action的整个调用过程中就有命令模式。
– 数据库事务机制的底层实现
– 命令的撤销和恢复

package cn.xiaomifeng1010.command;

/**
 * 真正的命令的执行者
 * @author Administrator
 *
 */
public class Receiver {
	public void action(){
		System.out.println("Receiver.action()");
	}
}
package cn.xiaomifeng1010.command;

//调用者/发起者
public class Invoke {
	
	private Command command;   //也可以通过容器List<Command>容纳很多命令对象,进行批处理。数据库底层的事务管理就是类似的结构!

	public Invoke(Command command) {
		super();
		this.command = command;
	} 
	
	//业务方法 ,用于调用命令类的方法
	public void call(){
		command.execute();
	}
	
	
}
package cn.xiaomifeng1010.command;

public interface Command {
	/**
	 * 这个方法是一个返回结果为空的方法。
	 * 实际项目中,可以根据需求设计多个不同的方法
	 */
	void execute();
}


class ConcreteCommand implements Command {
	
	private Receiver receiver;	//命令的真正的执行者
	
	public ConcreteCommand(Receiver receiver) {
		super();
		this.receiver = receiver;
	}

	@Override
	public void execute() {
		//命令真正执行前或后,执行相关的处理!
		receiver.action();
	}
	
}
package cn.xiaomifeng1010.command;

public class Client {
	public static void main(String[] args) {
		Command c = new ConcreteCommand(new Receiver());
		Invoke i = new Invoke(c);
		i.call();
	
		
//		new Receiver().action();
		
	}
}

2.17.2 命令模式的优缺点:

优点:

类间解耦

调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需调用Command抽象类的execute方法就可以,不需要了解到底是哪个接收者执行。

可扩展性

Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合。

命令模式结合其他模式会更优秀

命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式可以减少Command子类的膨胀问题。

缺点:

命令模式也是优缺点的,请看Command的子类:如果有N个命令,问题就出来了,Command的子类就可不是几个,而是N个,这个类膨胀得非常大,这个就需要读者在项目中慎重考虑使用。

2.18 解释器模式

2.18.1 解释器模式定义:

给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。

 

是一种不常用的设计模式
– 用于描述如何构成一个简单的语言解释器,主要用于使用面向对象语言开发的
编译器和解释器设计。
– 当我们需要开发一种新的语言时,可以考虑使用解释器模式。
– 尽量不要使用解释器模式,后期维护会有很大麻烦。在项目中,可以使用Jruby,Groovy、java的js引擎来替代解释器的作用,弥补java语言的不足。

解释器模式通用类图

 

AbstactExpression-------抽象解释器

具体的解释任务由各个实现类完成,具体的解释器分别由TerminalExpression和Non-terminal-Expression完成。

TerminalExpression----中介符表达式

实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。

NonterminalExpression---非终结符表达式

文法中的每条规则对应与一个非终结表达式。

Context----环境角色

具体到我们的例子中是采用HashMap代替。

2.18.2 解释器模式的优缺点

优点:

解释器是一个简单语法分析工具,它显著的优点就是扩展性,修改语法规则只要修改相应的非终结符表达式就可以了,若扩展语法,则只要增加非终结符类就可以了

缺点:

解释器模式会引起类膨胀

每个语法都要产生一个非终结符表达式,语法规则比较复杂时,就可能产生大量的类文件,为维护带来了非常多的麻烦

解释器模式采用递归调用方法

每个非终结符表达式只关心与自己有关的表达式,每个表达式需要知道最终的结果,必须一层一层地剥茧,无论是面向过程的语言还是面向对象的语言,递归都是在必要条件下使用的,它导致吊事非常复杂。想想看,如果要排查一个语法错误,我们是不是要一个断点一个断点地调试下去,知道最小的语法单元。

效率问题

解释器模式由于使用了大量的循环体和递归,效率是一个不容忽视的问题,特别是用于解析复杂、冗长的语法是效率是难以忍受的。

开发中常见的场景:
– EL表达式式的处理
– 正则表达式解释器
– SQL语法的解释器
– 数学表达式解析器
• 如现成的工具包:Math Expression String Parser、Expression4J等。
– MESP的网址: http://sourceforge.net/projects/expression-tree/
– Expression4J的网址: http://sourceforge.net/projects/expression4j/

2.19 访问者模式

2.19.1 访问者模式定义:

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

访问者模式通用类图

Vistor----抽象访问者

抽象类或者接口,声明访问者可以访问那些元素,具体到程序中就是visit方法的参数定义哪些对象时可以被访问的。

ConcreteVister-----具体访问者

它影响访问者访问到一个类后该怎么干,要做什么事情。

Element----抽象元素

接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。

ConcreteElement---具体元素

实现accept方法,通常是visitor。visit(this),基本上都形成了一种模式了。

ObjectStructure------结构对象

元素产生者:一般容纳在多个不同类,不同接口的容器,如List、Set、Map等,在项目中,一般很少抽象出这个角色。

开发中的场景(应用范围非常窄,了解即可):
– XML文档解析器设计
– 编译器的设计
– 复杂集合对象的处理

2.19.2 访问者模式的优缺点

优点:

符合单一职责原则

优秀的扩展性

灵活性非常高

缺点:

具体元素对访问者公布细节

具体元素变更比较困难

违背了依赖倒置原则

2.20 策略模式

策略模式定义:

定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。

策略模式通用类图

Context封装角色

它也叫上下文角色,起承上启下封装作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化

Strategy抽象策略角色

策略、算法家族的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性。

ConcreteStrategy 具体策略角色

事项抽象类中的操作,该类含有具体的算法。

2.20.2 策略模式的优缺点:

优点:

算法可以自由切换

这是策略模式本身定义的,只要实现抽象策略,它就成为策略家族的一个成员,通过封装角色对其进行封装,保证对外提供“可自由切换”的策略。

避免使用多重条件判断

扩展性良好

缺点:

策略类数量增多

所有的策略类都需要对外暴露

 

本质:
– 分离算法,选择实现。
• 开发中常见的场景:
– JAVASE中GUI编程中,布局管理
– Spring框架中,Resource接口,资源访问策略
– javax.servlet.http.HttpServlet#service()

场景:
-客户到银行办理业务:
①取号排队
②办理具体现金属账/企业/个人/理财业费
③给银行工作人员评分

2.20.3 策略模式实例演示:

package cn.xiaomifeng1010.strategy;

public interface Strategy {
	public double getPrice(double  standardPrice);
}
package cn.xiaomifeng1010.strategy;

public class OldCustomerFewStrategy implements Strategy {

	@Override
	public double getPrice(double standardPrice) {
		System.out.println("打八五折");
		return standardPrice*0.85;
	}

}

 

package cn.xiaomifeng1010.strategy;

public class OldCustomerManyStrategy implements Strategy {

	@Override
	public double getPrice(double standardPrice) {
		System.out.println("打八折");
		return standardPrice*0.8;
	}

}
package cn.xiaomifeng1010.strategy;

public class NewCustomerFewStrategy implements Strategy {

	@Override
	public double getPrice(double standardPrice) {
		System.out.println("不打折,原价");
		return standardPrice;
	}

}
package cn.xiaomifeng1010.strategy;

public class NewCustomerManyStrategy implements Strategy {

	@Override
	public double getPrice(double standardPrice) {
		System.out.println("打九折");
		return standardPrice*0.9;
	}

}
package cn.xiaomifeng1010.strategy;

/**
 * 负责和具体的策略类交互
 * 这样的话,具体的算法和直接的客户端调用分离了,使得算法可以独立于客户端独立的变化。
 * 如果使用spring的依赖注入功能,还可以通过配置文件,动态的注入不同策略对象,动态的切换不同的算法.
 * @author Administrator
 *
 */
public class Context {
	private Strategy strategy;	//当前采用的算法对象

	//可以通过构造器来注入
	public Context(Strategy strategy) {
		super();
		this.strategy = strategy;
	}
	//可以通过set方法来注入
	public void setStrategy(Strategy strategy) {
		this.strategy = strategy;
	}
	
	public void pringPrice(double s){
		System.out.println("您该报价:"+strategy.getPrice(s));
	}
	
	
}

client客户端

package cn.xiaomifeng1010.strategy;

public class Client {
	public static void main(String[] args) {
		
		Strategy s1 = new OldCustomerManyStrategy();
		Context ctx = new Context(s1);
		
		ctx.pringPrice(998);
		
	}
}

测试类:

package cn.xiaomifeng1010.strategy;

/**
 * 实现起来比较容易,符合一般开发人员的思路
 * 假如,类型特别多,算法比较复杂时,整个条件语句的代码就变得很长,难于维护。
 * 如果有新增类型,就需要频繁的修改此处的代码!
 * 不符合开闭原则!
 * @author Administrator
 *
 */
public class TestStrategy {
	public double getPrice(String type, double price) {

		if (type.equals("普通客户小批量")) {
			System.out.println("不打折,原价");
			return price;
		} else if (type.equals("普通客户大批量")) {
			System.out.println("打九折");
			return price * 0.9;
		} else if (type.equals("老客户小批量")) {
			System.out.println("打八五折");
			return price * 0.85;
		} else if (type.equals("老客户大批量")) {
			System.out.println("打八折");
			return price * 0.8;
		}
		return price;
	}

}

关系类图:

2.21 模板方法

2.21.1 模板方法定义:

定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

模板方法模式通用类图

模板方法模式确实非常简单,仅仅使用了java的继承机制,但它是一个应用非常广泛的模式。其中AbstractClass叫做抽象模板,它的方法分为两类:

基本方法

基本方法也叫作基本操作,是由子类实现的方法,并且在模板方法被调用。

模板方法

可以有一个或几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑。

注意:为了防止恶意的操作,一般末班方法都加上final关键字,不允许被覆写。

2.21.2 模板方法模式的优缺点

优点:

封装不变部分,扩展可变部分

提取公共部分代码,便于维护影响

行为由父类控制,子类实现

缺点:

按照我们的设计习惯,抽象类负责声明最抽象,最一般的事物属性和方法,实现类完成具体的事物属性和方法,但是模板方法模式却颠倒了,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响,这在复杂的项目中,会带来代码阅读的难度,而且也会让新手产生不适感。

2.21.3 案例演示:

client

package cn.xiaomifeng1010.templateMethod;

public class Client {
	public static void main(String[] args) {
		BankTemplateMethod btm = new DrawMoney();
		btm.process();
		
		//采用匿名内部类
		BankTemplateMethod btm2 = new BankTemplateMethod() {
			
			@Override
			public void transact() {
				System.out.println("我要存钱!");
			}
		};
		btm2.process();
		
		BankTemplateMethod btm3 = new BankTemplateMethod() {
			@Override
			public void transact() {
				System.out.println("我要理财!我这里有2000万韩币");
			}
		};
		
		btm3.process();
		
	}
}



class DrawMoney extends BankTemplateMethod {

	@Override
	public void transact() {
		System.out.println("我要取款!!!");
	}
	
}

 什么时候用到模板方法模式:
-实现一个算法时,整体步骤很固定。但是,某些部分易变。易变部分
可以抽象成出来F 供子类实现。
开发中常见的场景:
一非常频繁。各个框架、类库中都有他的影子。比如常见的有:
·数据库访问的封装
• Junit单元测试
• servlet中关于doGet/doPost方法调用
• Hibernate中模板程序
• spnng 中JDBCTemplate 、H ibernate Template等。

2.22 状态模式

2.22.1 状态模式定义

当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。

状态模式的核心是封装,状态的变更引起了行为的变更,从外部看起来就好像这个对象对应的类发生了改变一样。

                                                状态模式通用类图 

State-----抽象状态角色

接口或抽象类,负责对象状态定义,并且封装环境角色以实现状态切换。

ConcreteState-----具体状态角色

每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态处理,通俗来说,就是本状态下要做的事情,以及本状态如何过渡到其他状态。

Context------环境角色

定义客户端需要的接口,并且负责具体状态的切换。

2.22.2 状态模式的优缺点

优点:

结构清晰

避免了过多的switch...case或者if....else语句的使用,避免了程序的复杂性,提高系统的可维护性。

遵循设计原则

很好地体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就要增加子类,你要修改状态,你只要修改一个子类就可以了

封装性好

这也是状态模式的基本要求,状态变换放置到类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换。

缺点:

子类会太多,就是累膨胀。如果一个事物有很多个状态也不稀奇,如果完全使用状态模式就会有太多的子类,不好管理,这个需要大家在项目中自己衡量。其实有很多方式可以解决这个问题,如在数据库中建立一个状态表,然后根据状态执行相应的操作,这个也不复杂,看大家的习惯和嗜好了。

 

场景:

1、行为随状态改版而改变的场景

这也是状态设计的根本出发点,例如权限设计,人员的状态不同及时执行相同的行为结果也会不同,在这种情况下需要考虑使用状态模式。

2、条件、分支判断语句的替代者

在程序中大量使用switch语句或者if判断语句会导致程序结构不清晰,逻辑混乱,使用状态模式可以很好避免这一问题,它通过扩展子类实现条件的判断处理。

例如:
– 电梯的运行
• 维修、正常、自动关门、自动开门、向上运行、向下运行、消防状态
– 红绿灯
• 红灯、黄灯、绿灯
– 企业或政府系统
• 公文的审批状态
– 报销单据审批状态
– 假条审批
– 网上购物时,订单的状态
• 下单
• 已付款
• 已发货
• 送货中
• 已收货

状态模式的注意事项:

状态模式适用于当某个对象在它的状态发生改变时,它的行为也随着发生较大的变化,也就是说在行为受状态约束的情况下可以使用状态模式,而且使用时对象的状态最好不要超过5个。

2.22.3 具体实例演示:

各种状态: 

package cn.xiaomifeng1010.state;

public interface State {
	void handle();
}
package cn.xiaomifeng1010.state;

/**
 * 空闲状态
 * @author Administrator
 *
 */
public class FreeState implements State {

	@Override
	public void handle() {
		System.out.println("房间空闲!!!没人住!");
	}

}

 

package cn.xiaomifeng1010.state;

/**
 * 已预订状态
 * @author Administrator
 *
 */
public class BookedState implements State {

	@Override
	public void handle() {
		System.out.println("房间已预订!别人不能定!");
	}

}
package cn.xiaomifeng1010.state;

/**
 * 已入住状态
 * @author Administrator
 *
 */
public class CheckedInState implements State {

	@Override
	public void handle() {
		System.out.println("房间已入住!请勿打扰!");
	}

}

homecontext类(房间对象)

package cn.xiaomifeng1010.state;


/**
 * 房间对象
 * @author Administrator
 *
 */
public class HomeContext {
	//如果是银行系统,这个Context类就是账号。根据金额不同,切换不同的状态!
	
	private State state;
	
	
	public void setState(State s){
		System.out.println("修改状态!");
		state = s;
		state.handle();
	}
	
}

client类

package cn.xiaomifeng1010.state;


public class Client {
	public static void main(String[] args) {
		HomeContext ctx = new HomeContext();
		
		ctx.setState(new FreeState());
		ctx.setState(new BookedState());
		
	}
}

关系类图:

 开发中常见的场景:
– 银行系统中账号状态的管理
– OA系统中公文状态的管理
– 酒店系统中,房间状态的管理
– 线程对象各状态之间的切换

2.23 观察者模式

2.23.1 观察者模式定义:

观察者模式也叫发布订阅模式,定义对象键一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。

观察者模式通用类图

Subject-----被观察者

定义被观察者必须实现的职责,它必须能够动态地增加,取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。

Observer-----观察者

观察者受到消息后,即进行update(更新方法)操作,对接收到的信息进行处理。

ConcreteSubject-----具体的被观察者

定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。

ConcreteObserver-----具体的观察者

每个观察者在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑。

javaSE提供了java.util.Observable和java.util.Observer来实现观察者模式

2.23.2 观察者模式的优缺点

优点:

观察者和被观察者之间是抽象耦合

如此设计,则不管是增加观察者还是被观察者都非常容易扩展,而且在java中都已经实现的抽象层级的定义,在系统扩展方面根式得心应手。

建立一套触发机制

根据单一职责原则,梅格雷的职责是单一的,那么怎么把各个单一的职责串联成真实世界的复杂的逻辑关系呢?比如,我们去打猎,打死了一只母鹿,母鹿有三个幼崽,因失去了母鹿而饿死,尸体又被两只秃鹰争抢,因分配不均,秃鹰开始斗殴,然后羸弱的秃鹰死掉,生存下来的秃鹰,则因此kuodal扩大了地盘。。。这就是一个触发机制,形成了一个触发器,观察者模式可以完美地实现这里的链条形式

缺点:

观察者模式需要考虑一下开发效率和运行效率问题,一个被观察者,多个观察者,开发和调试就会比较复杂,而且在java中消息的通知默认是顺序执行,一个观察者卡壳,会影响整体的执行效果。在这种情况下,一般考虑采用异步的方式。

多级触发是的效果更是让人担忧,大家在设计时注意考虑。

2.23.3 观察者模式的使用场景:

关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。

事件多级触发场景。

跨系统的消息交换场景,如消息队列的处理机制。

开发中常见的具体场景:

开发中常见的场景:
– 聊天室程序的,服务器转发给所有客户端
– 网络游戏(多人联机对战)场景中,服务器将客户端的状态进行分发
– 邮件订阅
– Servlet中,监听器的实现
– Android中,广播机制
– JDK的AWT中事件处理模型,基于观察者模式的委派事件模型(Delegation Event
Model)
• 事件源----------------目标对象
• 事件监听器------------观察者
– 京东商城中,群发某商品打折信息

观察者模式的注意事项:

使用观察者模式也有以下两个重点问题解决:

广播链问题

异步处理问题

2.23.4 具体实例演示

package cn.xiaomifeng1010.observer;

public interface Observer {
	void  update(Subject subject);
}
package cn.xiaomifeng1010.observer;

public class ObserverA implements Observer {

	private int myState;   //myState需要跟目标对象的state值保持一致!
	
	
	@Override
	public void update(Subject subject) {
		myState = ((ConcreteSubject)subject).getState();
	}


	public int getMyState() {
		return myState;
	}
	public void setMyState(int myState) {
		this.myState = myState;
	}
	
	

}
package cn.xiaomifeng1010.observer;

import java.util.ArrayList;
import java.util.List;

public class Subject {
	
	protected List<Observer> list = new ArrayList<Observer>();
	
	public void registerObserver(Observer obs){
		list.add(obs);
	}
	public void removeObserver(Observer obs){
		list.add(obs);
	}

	//通知所有的观察者更新状态
	public void notifyAllObservers(){
		for (Observer obs : list) {
			obs.update(this);
		}
	}
	
}
package cn.xiaomifeng1010.observer;

public class ConcreteSubject extends Subject {
	
	private int state;

	public int getState() {
		return state;
	}

	public void setState(int state) {
		this.state = state;
		//主题对象(目标对象)值发生了变化,请通知所有的观察者
		this.notifyAllObservers();
	} 
	
	
	
	
	
}

 

package cn.xiaomifeng1010.observer;

public class Client {
	public static void main(String[] args) {
		//目标对象
		ConcreteSubject subject = new ConcreteSubject();
		
		//创建多个观察者
		ObserverA  obs1 = new ObserverA();
		ObserverA  obs2 = new ObserverA();
		ObserverA  obs3 = new ObserverA();
		
		//将这三个观察者添加到subject对象的观察者队伍中
		subject.registerObserver(obs1);
		subject.registerObserver(obs2);
		subject.registerObserver(obs3);
		
		
		//改变subject的状态
		subject.setState(3000);
		System.out.println("########################");
		//我们看看,观察者的状态是不是也发生了变化
		System.out.println(obs1.getMyState());
		System.out.println(obs2.getMyState());
		System.out.println(obs3.getMyState());
		
		//改变subject的状态
		subject.setState(30);
		System.out.println("########################");
		//我们看看,观察者的状态是不是也发生了变化
		System.out.println(obs1.getMyState());
		System.out.println(obs2.getMyState());
		System.out.println(obs3.getMyState());
		
		
	}
}

 关系类图:

2.24 备忘录模式

2.24.1 备忘录模式定义:

备忘录模式提供了一种弥补真实世界缺陷的方法,让“后悔药”在程序的世界中真实可行。

定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

通俗来说。备忘录模式就是一个对象的备份模式,提供了一种程序数据的备份方法。

                                        备忘录模式通用类图

Originator------发起人角色

记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。

Memento-----备忘录角色

负责存储Originator发起人对象内部状态,在需要的时候提供发起人需要的内部状态。

Caretaker-----备忘录管理员角色

对备忘录进行管理、保存和提供备忘录。

2.24.2 开发中常见的应用场景:
– 棋类游戏中的,悔棋
– 普通软件中的,撤销操作
– 数据库软件中的,事务管理中的,回滚操作
– Photoshop软件中的,历史记录

2.24.3 具体实例演示:

package cn.xiaomifeng1010.memento;

/**
 * 源发器类
 * @author Administrator
 *
 */
public class Emp {
	private String ename;
	private int age;
	private double salary;
	
	
	//进行备忘操作,并返回备忘录对象
	public EmpMemento  memento(){
		return new EmpMemento(this);
	}
	
	
	//进行数据恢复,恢复成制定备忘录对象的值
	public void recovery(EmpMemento mmt){
		this.ename = mmt.getEname();
		this.age = mmt.getAge();
		this.salary = mmt.getSalary();
	}
	
	
	public Emp(String ename, int age, double salary) {
		super();
		this.ename = ename;
		this.age = age;
		this.salary = salary;
	}
	public String getEname() {
		return ename;
	}
	public void setEname(String ename) {
		this.ename = ename;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public double getSalary() {
		return salary;
	}
	public void setSalary(double salary) {
		this.salary = salary;
	}
	
	
}
package cn.xiaomifeng1010.memento;

/**
 * 备忘录类
 * @author Administrator
 *
 */
public class EmpMemento {
	private String ename;
	private int age;
	private double salary;
	
	
	public EmpMemento(Emp e) {
		this.ename = e.getEname();
		this.age = e.getAge();
		this.salary = e.getSalary();
	}
	
	
	public String getEname() {
		return ename;
	}
	public void setEname(String ename) {
		this.ename = ename;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public double getSalary() {
		return salary;
	}
	public void setSalary(double salary) {
		this.salary = salary;
	}
	
	
	
}
package cn.xiaomifeng1010.memento;

/**
 * 负责人类
 * 负责管理备忘录对象
 * @author Administrator
 *
 */
public class CareTaker {
	
	private EmpMemento memento;

//	private List<EmpMemento> list = new ArrayList<EmpMemento>();
	
	
	
	public EmpMemento getMemento() {
		return memento;
	}

	public void setMemento(EmpMemento memento) {
		this.memento = memento;
	}
	
	
	
	
}
package cn.xiaomifeng1010.memento;

public class Client {
	public static void main(String[] args) {
		CareTaker taker = new CareTaker();
		
		Emp emp = new Emp("高淇", 18, 900);
		System.out.println("第一次打印对象:"+emp.getEname()+"---"+emp.getAge()+"---"+emp.getSalary());
		
		taker.setMemento(emp.memento());   //备忘一次
		
		emp.setAge(38);
		emp.setEname("搞起");
		emp.setSalary(9000);
		System.out.println("第二次打印对象:"+emp.getEname()+"---"+emp.getAge()+"---"+emp.getSalary());
		
		emp.recovery(taker.getMemento()); //恢复到备忘录对象保存的状态
		
		System.out.println("第三次打印对象:"+emp.getEname()+"---"+emp.getAge()+"---"+emp.getSalary());
		
	}
}

 关系类图:

 23种设计模式告一段落,汇总一下:

 

本文部分内容引用自《设计模式之禅第二版》作者:秦小波

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值