设计模式详解(6) -- 行为型模式详解下

本系列文章:
设计模式详解(1)-- 初识设计模式
设计模式详解(2)-- 创建型模式详解
设计模式详解(3)-- 结构型模式详解上
设计模式详解(4)-- 结构型模式详解下
设计模式详解(5)-- 行为型模式详解上
设计模式详解(6) – 行为型模式详解下

本博客专门介绍行为型模式中的观察者模式,状态模式,策略模式,模板模式,访问者模式。

观察者模式(Observer Pattern)

观察者模式在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新。

结构
  • 抽象被观察者角色:也就是一个抽象主题,它把所有对观察者对象的引用保存在一个集合中,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
  • 抽象观察者角色:为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
  • 具体被观察者角色:也就是一个具体的主题,在集体主题的内部状态改变时,所有登记过的观察者发出通知。
  • 具体观察者角色:实现抽象观察者角色所需要的更新接口,一边使本身的状态与制图的状态相协调。

又到了大家喜闻乐见的举例时间啦,这次我们走进生活,模拟一下微信公众号的推送服务好不好?如果我们关注了这个公众号,就给我们发送推送信息,没有关注就不发送。

示例

我们先创建一个被观察者接口

public interface Observerable {

	//增加观察者
	void addObserver(Observer o);
	//删除观察者
	void removeObserver(Observer o);
	//通知所有观察者
    void notifyObserver();
}

继续创建一个观察者接口

public interface Observer {

	void getMessage(String message);
}

创建具体被观察者,微信公众号

public class WechatServer implements Observerable {

	List<Observer> list;
	//要传送的信息
	String message;
	
	public WechatServer() {
		list = new ArrayList<Observer>();
	}
	
	@Override
	public void addObserver(Observer o) {
		list.add(o);
	}

	@Override
	public void removeObserver(Observer o) {
		list.remove(o);
	}

	@Override
	public void notifyObserver() {
		for (Observer observer : list) {
			observer.getMessage(message);
		}
	}
	
	public void update(String message) {
		this.message = message;
		System.out.println("微信公众号已经发布了 " + message + " 信息");
		//通知所有观察者
		notifyObserver();
	}

}

创建具体观察者

public class Person implements Observer{

	private String name;
	private String message;
	
	public Person(String name) {
		this.name = name;
	}
	
	@Override
	public void getMessage(String message) {
		this.message = message;
		System.out.println(name + "已经收到了" + message + "信息");
	}
}

测试一下

public class Demo {

	public static void main(String[] args) {
		WechatServer wechatServer = new WechatServer();
		
		Observer person1 = new Person("张无忌");
		Observer person2 = new Person("赵敏");
		Observer person3 = new Person("周芷若");
				
		wechatServer.addObserver(person1);
		wechatServer.addObserver(person2);
		wechatServer.addObserver(person3);
		
		//微信公众号推送张无忌喜欢赵敏
		wechatServer.update("张无忌喜欢赵敏!!!");
		
		//周芷若看了之后很生气,直接取消关注了
		wechatServer.removeObserver(person3);
		
		//继续推送消息
		wechatServer.update("张无忌跟赵敏结婚了!!");
	}
}

微信公众号已经发布了 张无忌喜欢赵敏!!! 信息
张无忌已经收到了张无忌喜欢赵敏!!!信息
赵敏已经收到了张无忌喜欢赵敏!!!信息
周芷若已经收到了张无忌喜欢赵敏!!!信息
微信公众号已经发布了 张无忌跟赵敏结婚了!! 信息
张无忌已经收到了张无忌跟赵敏结婚了!!信息
赵敏已经收到了张无忌跟赵敏结婚了!!信息

可以看到,当周芷若取消观察之后,她再也接收不到信息了。

优点
  • 观察者与被观察者之间是属于轻度的关联关系,并且是抽象耦合的,这样,对于两者来说都比较容易进行扩展。
  • 观察者模式是一种常用的触发机制,它形成一条触发链,依次对各个观察者的方法进行处理。
缺点

由于是链式触发,当观察者比较多的时候,性能问题是比较令人担忧的。并且,在链式结构中,比较容易出现循环引用的错误,造成系统假死。

状态模式(State Pattern)

先直接上定义,当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。
话说作为苦逼的码农,电脑的使用步骤相信大家已经很熟悉了,开机,做事情,关机,这三个步骤是必须不可变的,我们今天就通过电脑的使用来讲解状态模式。

在没有学习状态模式之前,我们写出的代码肯定如下所示,用一大堆的if-else语句。

final static int OPEN_COMPUTER = 0;//开机
final static int DO_SOMETHING = 1;//做事情
final static int CLOSE_COMPUTER = 2;//关机
		
		
public void test() {
	if(state == OPEN_COMPUTER) {
		//do something
	}else if(state == DO_SOMETHING) {
		//do something
	}else if(state == CLOSE_COMPUTER) {
		//do something
	}
}

现在还好,只有三个分支,但如果项目一大, 产生二三十个分支,有这种经历的码农都知道这种被支配的恐惧。这种代码不仅非常难以管理,难以扩展,而且不符合开闭原则。为改变这种状况,我们急需学习新的设计模式,看,状态模式浓妆上场了!

使用场景
  • 行为随状态的改变而改变。
  • 如果需要使用大量的条件、分支判断。
结构
  • State抽象状态角色 接口或抽象类,负责对象状态定义,并且封装环境角色以实现状态切换。
  • ConcreteState具体状态角色 具体状态主要有两个职责:一是处理本状态下的事情,二是从本状态如何过渡到其他状态。
  • Context环境角色 定义客户端需要的接口,并且负责具体状态的切换。
示例

下面模拟电脑的使用步骤

我们先来实现State接口

public interface State {

	void open();//开机
	void doSomething();//做事情
	void close();//关机
	
	void setContext(Context context);//为状态设置上下文
}

接着我们实现具体的三种状态

//已经关机的状态,只能实现开机
public class CloseState implements State {

	private Context context;
	
	@Override
	public void open() {
		System.out.println("电脑成功开机!!");
		context.setState(Context.openState);
	}

	@Override
	public void doSomething() {
		System.out.println("电脑还没有开机,不能做事情");
	}

	@Override
	public void close() {
		System.out.println("电脑还没有开机,不能关机");
	}

	@Override
	public void setContext(Context context) {
		this.context = context;
	}
}

//已经开机的状态,只能做事情
public class OpenState implements State{

	private Context context;
	
	@Override
	public void open() {
		System.out.println("电脑已经开过机了!!");
	}

	@Override
	public void doSomething() {
		System.out.println("电脑成功做了一些事情");
		context.setState(Context.doState);
	}

	@Override
	public void close() {
		System.out.println("请先做点事情再关机");
	}
	
	@Override
	public void setContext(Context context) {
		this.context = context;
	}
}

//已经做完事情的状态,可以关机
public class DoState implements State{

	private Context context;
	
	@Override
	public void open() {
		System.out.println("电脑已经是开机状态了");
	}

	@Override
	public void doSomething() {
		System.out.println("电脑已经做完事情了");
	}

	@Override
	public void close() {
		System.out.println("明智的选择!成功关机");
		context.setState(Context.closeState);
	}

	@Override
	public void setContext(Context context) {
		this.context = context;
	}
}

实现Context

public class Context {

	public static final CloseState closeState = new CloseState();
	public static final OpenState openState = new OpenState();
	public static final DoState doState = new DoState();
	
	private State nowState; // 设置当前状态
	
	public void setState(State nowState) {
		this.nowState = nowState;
		nowState.setContext(this);
	}
	
	public void open() {
		this.nowState.open();
	}
	
	public void doSomething() {
		this.nowState.doSomething();
	}
	
	public void close() {
		this.nowState.close();
	}
}

测试一下

public class Demo {

	public static void main(String[] args) {
		Context context = new Context();
		context.setState(Context.closeState);//电脑一开始是关机状态
		
		//做一些不能完成的动作
		context.doSomething();
		context.close();
		
		//顺序进行开机,做事情,关机
		context.open();
		context.doSomething();
		context.close();
		
	}
}

结果

电脑还没有开机,不能做事情
电脑还没有开机,不能关机
电脑成功开机!!
电脑成功做了一些事情
明智的选择!成功关机

优点
  • 体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就要增加子类,你要修改状态,你只修改一个子类就可以了。
  • 符合迪米特法则。
  • 封装性非常好
  • 结构清晰,避免了过多的switch…case或者if…else语句的使用,避免了程序的复杂性,提高可维护性。

策略模式(Strategy Pattern)

策略模式定义了一系列的算法,并将每一个算法封装起来,使每个算法可以相互替代,使算法本身和使用算法的客户端分割开来,相互独立。

结构
  • 策略接口角色IStrategy:用来约束一系列具体的策略算法,策略上下文角色ConcreteStrategy使用此策略接口来调用具体的策略所实现的算法。
  • 具体策略实现角色ConcreteStrategy:具体的策略实现,即具体的算法实现。
  • 策略上下文角色StrategyContext:策略上下文,负责和具体的策略实现交互,通常策略上下文对象会持有一个真正的策略实现对象,策略上下文还可以让具体的策略实现从其中获取相关数据,回调策略上下文对象的方法。
示例

笔者家里楼下的洗衣店,对新客户有6折优惠,对VIP客户有9折优惠,对老客户没有优惠。。。。(一脸蒙逼),下面我们使用策略模式来模拟。

先建立一个公共策略接口

//公共策略接口
public interface IStrategy {

	double getDiscount(double money);
}

然后我们设置几个具体策略

//老客户的具体策略实现
public class OldStrategy implements IStrategy {

	@Override
	public double getDiscount(double money) {
		System.out.println("这里是老用户的具体策略实现,不打折");
		return money;
	}

}

//新客户的具体策略实现
public class NewPeopleStrategy implements IStrategy{

	@Override
	public double getDiscount(double money) {
		System.out.println("这里是新客户的具体策略实现,可以打六折");
		return money*0.6;
	}
}

//VIP用户的具体策略实现
public class VIPStrategy implements IStrategy{

	@Override
	public double getDiscount(double money) {
		System.out.println("这里是VIP用户的具体策略实现,可以打九折");
		return money*0.9;
	}

}

构建策略上下文

public class StrategyContext {

	private IStrategy iStrategy;
	
	public void setIStrategy(IStrategy iStrategy) {
		this.iStrategy = iStrategy;
	}
	
	public double getDiscount(double money) {
		return iStrategy.getDiscount(money);
	}
}

测试一下

public class Demo {
	
	public static void main(String[] args) {
		//创建VIP客户的策略
		IStrategy iStrategy = new VIPStrategy();
		//创建策略上下文,并设置具体策略
		StrategyContext strategyContext = new StrategyContext();
		strategyContext.setIStrategy(iStrategy);
		//原价
		double money = 300;
		System.out.println("原价是" + money);
		double nowMoney = strategyContext.getDiscount(money);
		System.out.println("现价是" + nowMoney);
	}
}

原价是300.0
这里是VIP用户的具体策略实现,可以打九折
现价是270.0

优点
  • 策略模式的功能就是通过抽象、封装来定义一系列的算法,使得这些算法可以相互替换,所以为这些算法定义一个公共的接口,以约束这些算法的功能实现。如果这些算法具有公共的功能,可以将接口变为抽象类,将公共功能放到抽象父类里面。
  • 策略模式的一系列算法是可以相互替换的、是平等的,写在一起就是if-else组织结构,如果算法实现里又有条件语句,就构成了多重条件语句,可以用策略模式,避免这样的多重条件语句。
  • 扩展性更好:在策略模式中扩展策略实现非常的容易,只要新增一个策略实现类,然后在使用策略实现的地方,使用这个新的策略实现就好了。
缺点
  • 客户端必须了解所有的策略,清楚它们的不同:如果由客户端来决定使用何种算法,那客户端必须知道所有的策略,清楚各个策略的功能和不同,这样才能做出正确的选择,但是这暴露了策略的具体实现。
  • 增加了对象的数量:由于策略模式将每个具体的算法都单独封装为一个策略类,如果可选的策略有很多的话,那对象的数量也会很多。
  • 只适合偏平的算法结构:由于策略模式的各个策略实现是平等的关系(可相互替换),实际上就构成了一个扁平的算法结构。即一个策略接口下面有多个平等的策略实现(多个策略实现是兄弟关系),并且运行时只能有一个算法被使用。这就限制了算法的使用层级,且不能被嵌套。

模板模式(Template Pattern)

模板模式,就是定义一个模板结构,将具体内容延迟到子类去实现。其实可以把模版模式看作基于继承的,目的是在不改变模板结构的前提下在子类中重新定义模板中的内容。

程序猿要写代码啦!大家都知道不同品种的程序猿在写代码这件事上,虽然某些步骤可能会不同,但大致的流程还是相同的,我们可以将其抽象成打开电脑,打开编辑器,开始敲代码,编译,运行这五个流程。我们假定Java程序猿与php程序猿的写代码流程上,只有打开编辑器,敲代码这两步不同,其余步骤相同。下面我们使用代码模拟。

示例

创建抽象模板结构

public abstract class Programmer {

	//子类不能覆盖这个方法
	final public void work() {
		//打开电脑
		this.openComputer();
		//打开编辑器
		this.openEditor();
		//敲代码
		this.writingCode();
		//编译
		this.compile();
		//运行
		this.run();
	}
	
	//打开电脑可复用,直接实现
	public void openComputer() {
		System.out.println("程序猿打开电脑");
	}
	
	//打开编辑器不可复用,让子类实现
	abstract public void openEditor();
	
	//敲代码不可复用,让子类实现
	abstract public void writingCode();
	
	//编译可复用,直接实现
	public void compile() {
		System.out.println("程序在编译");
	}
	
	//运行可复用,直接实现
	public void run() {
		System.out.println("程序在运行");
	}
}

创建两个具体模板

public class JavaProgrammer extends Programmer {

	@Override
	public void openEditor() {
		System.out.println("Java程序猿打开Java编辑器");
	}

	@Override
	public void writingCode() {
		System.out.println("Java程序猿写Java代码");
	}

}

public class PhpProgrammer extends Programmer {

	@Override
	public void openEditor() {
		System.out.println("PHP程序猿打开PHP编辑器");
	}

	@Override
	public void writingCode() {
		System.out.println("PHP程序猿写PHP代码");
	}

}

测试一下

public class Demo {

	public static void main(String[] args) {
		//Java程序猿开始工作
		Programmer javaProgrammer = new JavaProgrammer();
		javaProgrammer.work();
		
		System.out.println("-----------分割线-----------");
		
		//PHP程序猿开始工作
		Programmer phpProgrammer = new PhpProgrammer();
		phpProgrammer.work();
	}
}

程序猿打开电脑
Java程序猿打开Java编辑器
Java程序猿写Java代码
程序在编译
程序在运行
-----------分割线-----------
程序猿打开电脑
PHP程序猿打开PHP编辑器
PHP程序猿写PHP代码
程序在编译
程序在运行

优点
  • 提高代码复用性 将相同部分的代码放在抽象的父类中
  • 提高了拓展性 将不同的代码放入不同的子类中,通过对子类的扩展增加新的行为
  • 实现了反向控制 通过一个父类调用其子类的操作,通过对子类的扩展增加新的行为,实现了反向控制,符合“开闭原则”
缺点

引入了抽象类,每一个不同的实现都需要一个子类来实现,导致类的个数增加,从而增加了系统实现的复杂度。

访问者模式(Visitor Pattern)

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

结构
  • 抽象访问者:抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法中的参数定义哪些对象是可以被访问的。
  • 访问者:实现抽象访问者所声明的方法,它影响到访问者访问到一个类后该干什么,要做什么事情。
  • 抽象元素类:接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。抽象元素一般有两类方法,一部分是本身的业务逻辑,另外就是允许接收哪类访问者来访问。
  • 元素类:实现抽象元素类所声明的accept方法,通常都是visitor.visit(this),基本上已经形成一种定式了。
  • 结构对象:一个元素的容器,一般包含一个容纳多个不同类、不同接口的容器,如List、Set、Map等,在项目中一般很少抽象出这个角色。

小明开了一家公司,这家公司有一个需求,老板跟财务都需要对公司的收入与支出进行一定程度的了解(老板只需要知道总额,而财务需要对每一笔收入与支出进行了解),下面我们用访问者模式进行模拟。

示例

我们先构建一个账单接口

public interface Bill {

	void accept(View view);
}

构建消费账单,收入账单

//消费
public class ConsumerBill implements Bill{

	//消费金额
	private int money;
	
	private String name;
	
	@Override
	public void accept(View view) {
		view.visit(this);
	}

	public ConsumerBill(int money,String name){
		this.money = money;
		this.name = name;
	}
	
	public int getMoney() {
		return money;
	}
	
	public String getName() {
		return name;
	}
}

//收入
public class IncomeBill implements Bill{

	//收入金额
	private int money;
	
	private String name;
	
	@Override
	public void accept(View view) {
		view.visit(this);
	}
	
	public IncomeBill(int money,String name) {
		this.money = money;	
		this.name = name;
	}

	public int getMoney() {
		return money;
	}
	
	public String getName() {
		return name;
	}
}

访问者接口

public interface View {

	//查看消费
	void visit(ConsumerBill consumerBill);
	
	//查看收入
	void visit(IncomeBill incomeBill);
}

具体访问者

//老板
public class Boss implements View{

	//总收入
	private int allIncome;
	//总消费
	private int allConsumer;
	
	
	//查看消费
	@Override
	public void visit(ConsumerBill consumerBill) {
		allConsumer += consumerBill.getMoney();
	}

	//查看收入
	@Override
	public void visit(IncomeBill incomeBill) {
		allIncome += incomeBill.getMoney();
	}
	
	public void seeAllIncome() {
		System.out.println("老板发现公司的总收入为" + allIncome);
	}
	
	public void seeAllConsumer() {
		System.out.println("老板发现公司的总消费为" + allConsumer);
	}
}

//财务
public class Finance implements View{

	@Override
	public void visit(ConsumerBill consumerBill) {
		if(consumerBill.getName().equals("consumer")) {
			System.out.println("财务已收到一笔消费" + consumerBill.getMoney());
		}
	}

	@Override
	public void visit(IncomeBill incomeBill) {
		if(incomeBill.getName().equals("income")) {
			System.out.println("财务已收到一笔收入" + incomeBill.getMoney());
		}
	}
	
}

构建账单

//账单
public class Account {

	private List<Bill> listBill = new ArrayList<Bill>();
	
	//增加数据
	public void add(Bill bill) {
		listBill.add(bill);
	}
	
	//增加访问者
	public void show(View view) {
		for (Bill bill : listBill) {
			bill.accept(view);
		}
	}
}

测试

public class Demo {

	public static void main(String[] args) {
		Bill consumerBill1 = new ConsumerBill(100,"consumer");
		Bill consumerBill2 = new ConsumerBill(333,"consumer");
		Bill incomeBill1 = new IncomeBill(211,"income");
		Bill incomeBill2 = new IncomeBill(444,"income");
		
		//构建账单
		Account account = new Account();
		account.add(incomeBill1);
		account.add(incomeBill2);
		account.add(consumerBill1);
		account.add(consumerBill2);
		
		//创建访问者
		Boss boss = new Boss(); 
		Finance finance = new Finance();
		
		//接受访问者
		account.show(boss);
		account.show(finance);
		
		System.out.println("-----------分割线--------------");
		
		//查看公司总收入总消费
		boss.seeAllConsumer();
		boss.seeAllIncome();
	}
}

财务已收到一笔收入211
财务已收到一笔收入444
财务已收到一笔消费100
财务已收到一笔消费333
-----------分割线--------------
老板发现公司的总消费为433
老板发现公司的总收入为655

优点
  • 符合单一职责原则:凡是适用访问者模式的场景中,元素类中需要封装在访问者中的操作必定是与元素类本身关系不大且是易变的操作,使用访问者模式一方面符合单一职责原则,另一方面,因为被封装的操作通常来说都是易变的,所以当发生变化时,就可以在不改变元素类本身的前提下,实现对变化部分的扩展。
  • 扩展性良好:元素类可以通过接受不同的访问者来实现对不同操作的扩展。

参考:https://www.cnblogs.com/luohanguo/p/7825656.html
https://blog.csdn.net/zhengzhb/article/details/7471978#commentBox
https://www.jianshu.com/p/67ad1915fd62
https://blog.csdn.net/shuangde800/article/details/10132825
https://www.cnblogs.com/lewis0077/p/5133812.html
https://blog.csdn.net/carson_ho/article/details/54910518
https://blog.csdn.net/zhengzhb/article/details/7489639#commentBox
https://www.jianshu.com/p/80b9cd7c0da5

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值