本系列文章:
设计模式详解(1)-- 初识设计模式
设计模式详解(2)-- 创建型模式详解
设计模式详解(3)-- 结构型模式详解上
设计模式详解(4)-- 结构型模式详解下
设计模式详解(5)-- 行为型模式详解上
设计模式详解(6) – 行为型模式详解下
本博客专门介绍行为型模式中的责任链模式,命令模式,解释器模式,迭代器模式,中介者模式,备忘录模式。
责任链模式(Chain of Responsibility Pattern)
责任链模式是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
又到了我们喜闻乐见的举例时间了。小明是一位苦逼的程序猿,这段时间手头比较紧,想去找贷款公司借20万应急。他自然而然地找到一个朋友,贷款公司业务员,直接说想借点钱。业务员说没问题啊,随便借,然后问想借多少,小明说20万。业务员苦笑着说,不行啊,我只能处理3万以下的生意,想借更多得去找我们的经理。根据业务员的提示,找到了经理,却发现经理只能处理10万以下的业务,想借更多还得去找老板。最终我找到了老板,在答应九出十三归后,顺利地拿到了钱。。。。这件事情可算是结束了。
下面我们用代码进行模拟。
责任链模式的结构
我们先来说说它的结构,主要分为两个部分,包括抽象处理者和具体处理者。
- 抽象处理者(Handler)角色:定义出一个处理请求的接口。如果需要,接口可以定义
出一个方法以设定和返回对下家的引用。这个角色通常由一个Java抽象类或者Java接口实现。 - 具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。
示例
创建一个抽象处理者
//抽象处理者,用来处理借钱请求
public abstract class Handle {
//后继
protected Handle successor;
public void setSuccessor(Handle successor) {
this.successor = successor;
}
//处理借钱请求
public abstract void handling(int money);
}
接着我们继续创建具体处理者,分别为业务员,经理,老板
//业务员处理借钱请求
public class Sales extends Handle{
@Override
public void handling(int money) {
if(money <= 30000) {
System.out.println("业务员借出了" + money + "人民币");
}else {
System.out.println("业务员处理不了这个请求");
//向上级传递请求
successor.handling(money);
}
}
}
//经理处理借钱请求
public class Manager extends Handle{
@Override
public void handling(int money) {
if(money <= 100000) {
System.out.println("经理借出了" + money + "人民币");
}else {
System.out.println("经理处理不了这个请求");
//向上级传递请求
successor.handling(money);
}
}
}
//老板处理借钱请求
public class Boss extends Handle{
@Override
public void handling(int money) {
System.out.println("老板借出了" + money + "人民币");
}
}
测试一下责任链模式
public class Demo {
public static void main(String[] args) {
Handle sales = new Sales();
Handle manager = new Manager();
Handle boss = new Boss();
//组装成责任链
sales.setSuccessor(manager);
manager.setSuccessor(boss);
sales.handling(200000);
}
}
业务员处理不了这个请求
经理处理不了这个请求
老板借出了200000人民币
优点
实现了解耦,符合开闭原则,在这里面调用者不需要知道具体的传递过程,他只需要知道最终的结果被处理了。而且这个链表的结构可以被灵活的更改重组。
缺点
首先从性能上说起,一个是调用时间,如果链表在最开始被处理了还好,万一链表跑到了最后一个才被处理,那么他的调用时间肯定会比不适用责任链模式的效率要低一些;第二是内存的问题,我们会构造出很多的链表节点对象,但是有些对象在我们的应用场景中是不会用到的,所以大大的消耗了我们的内存。
命令模式(Command Pattern)
转载自 23种设计模式(10):命令模式
顾名思义,命令模式就是对命令的封装,就是将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
结构
由于命令模式比较难理解,我们先来讲讲命令模式的结构。
- Command类:是一个抽象类,类中对需要执行的命令进行声明,一般来说要对外公布一个execute方法用来执行命令。
- ConcreteCommand类:Command类的实现类,对抽象类中声明的方法进行实现。
- Client类:最终的客户端调用类。
- Invoker类:调用者,负责调用命令。
- Receiver类:接收者,负责接收命令并且执行命令。
标准示例
class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void action(){
this.command.execute();
}
}
abstract class Command {
public abstract void execute();
}
class ConcreteCommand extends Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver){
this.receiver = receiver;
}
public void execute() {
this.receiver.doSomething();
}
}
class Receiver {
public void doSomething(){
System.out.println("接受者-业务逻辑处理");
}
}
public class Client {
public static void main(String[] args){
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
//客户端直接执行具体命令方式(此方式与类图相符)
command.execute();
//客户端通过调用者来执行命令
Invoker invoker = new Invoker();
invoker.setCommand(command);
invoker.action();
}
}
通过代码我们可以看到,当我们调用时,执行的时序首先是调用者类,然后是命令类,最后是接收者类。也就是说一条命令的执行被分成了三步,它的耦合度要比把所有的操作都封装到一个类中要低的多,而这也正是命令模式的精髓所在:把命令的调用者与执行者分开,使双方不必关心对方是如何操作的。
优点
- 命令模式的封装性很好:每个命令都被封装起来,对于客户端来说,需要什么功能就去调用相应的命令,而无需知道命令具体是怎么执行的。比如有一组文件操作的命令:新建文件、复制文件、删除文件。如果把这三个操作都封装成一个命令类,客户端只需要知道有这三个命令类即可,至于命令类中封装好的逻辑,客户端则无需知道。
- 命令模式的扩展性很好,在命令模式中,在接收者类中一般会对操作进行最基本的封装,命令类则通过对这些基本的操作进行二次封装,当增加新命令的时候,对命令类的编写一般不是从零开始的,有大量的接收者类可供调用,也有大量的命令类可供调用,代码的复用性很好。比如,文件的操作中,我们需要增加一个剪切文件的命令,则只需要把复制文件和删除文件这两个命令组合一下就行了,非常方便。
缺点
命令如果很多,开发起来就要头疼了。特别是很多简单的命令,实现起来就几行代码的事,而使用命令模式的话,不用管命令多简单,都需要写一个命令类来封装。
适用场景
对于大多数请求-响应模式的功能,比较适合使用命令模式,正如命令模式定义说的那样,命令模式对实现记录日志、撤销操作等功能比较方便。
解释器模式(Interpreter Pattern)
先跳过,日后补上
迭代器模式(Iterator Pattern)
迭代器(Iterator)模式,又叫做游标(Cursor)模式,即提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
相信大家对这段代码不陌生吧,作用便是循环打印一个字符串集合,实际上用到了迭代器模式。迭代器模式主要用于遍历集合,几乎所有的集合类都使用了迭代器模式来提供遍历方法。
结构
- 迭代器角色(Iterator):迭代器角色负责定义访问和遍历元素的接口。
- 具体迭代器角色(Concrete Iterator):具体迭代器角色要实现迭代器接口,并要记录遍历中的当前位置。
- 容器角色(Container):容器角色负责提供创建具体迭代器角色的接口。
- 具体容器角色(Concrete Container):具体容器角色实现创建具体迭代器角色的接口——这个具体迭代器角色于该容器的结构相关。
示例
我们先创建迭代器角色接口
public interface Iterator {
//返回集合的下一个元素
public Object next();
//判断集合是否还有元素
public boolean hasNext();
}
然后我们构建具体迭代器角色
public class ConcreteIterator implements Iterator {
private List list;
private int cursor = 0;
public ConcreteIterator(List list) {
this.list = list;
}
@Override
public Object next() {
Object object = null;
if(this.hasNext()) {
object = this.list.get(cursor++);
}
return object;
}
@Override
public boolean hasNext() {
return !(this.cursor == list.size());
}
}
然后见着一个容器角色接口
public interface Aggregate {
void add(Object object);
void remove(Object object);
Iterator iterator();
}
创建一个具体容器角色
public class ConcreteAggregate implements Aggregate {
private List list = new ArrayList();
@Override
public void add(Object object) {
list.add(object);
}
@Override
public void remove(Object object) {
list.remove(object);
}
@Override
public Iterator iterator() {
return new ConcreteIterator(list);
}
}
测试
public class Demo {
public static void main(String[] args) {
Aggregate concreteAggregate = new ConcreteAggregate();
concreteAggregate.add("aaa");
concreteAggregate.add("ddd");
concreteAggregate.add("ppp");
Iterator iterator = concreteAggregate.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
其实大家可以把concreteAggregate想象成一个集合的。
优点
- 简化了遍历方式。
- 可以提供多种遍历方式,比如说对有序列表,我们可以根据需要提供正序遍历,倒序遍历两种迭代器,用户用起来只需要得到我们实现好的迭代器,就可以方便的对集合进行遍历了。
- 封装性良好,用户只需要得到迭代器就可以遍历,而对于遍历算法则不用去关心。
缺点
对于比较简单的遍历(像数组或者有序列表),使用迭代器方式遍历较为繁琐。
使用场景
有集合容器,必有迭代器。
中介者模式(Mediator Pattern)
中介者模式就是用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
中介者主要是通过中介对象来封装对象之间的关系,使之各个对象在不需要知道其他对象的具体信息情况下通过中介者对象来与之通信。同时通过引用中介者对象来减少系统对象之间关系,提高了对象的可复用和系统的可扩展性。
对象与对象之间的关系可能非常复杂,若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。对此,为了减少对象之间的关联关系,我们可以构建一个中介者,使每一个对象不与另外的对象进行通信,只与中介者进行通信,从而将复杂关系的网状结构变成结构简单的以中介者为核心的星形结构。
接下来进行代码模拟,假设有一个租客与一个包租婆,他们之间不直接交流,而是通过中介者进行交流。
示例
我们先创建一个抽象对象
public abstract class Person {
protected String name;
protected Mediator mediator;
public Person(String name,Mediator mediator) {
this.name = name;
this.mediator = mediator;
}
public String getName() {
return name;
}
abstract public void sendMessage(String message);
}
然后创建两个具体对象,分别为租客和房东
public class Tenant extends Person{
public Tenant(String name, Mediator mediator) {
super(name, mediator);
}
//与中介者进行通信
public void contact(String message) {
mediator.chat(this, message);
}
@Override
public void sendMessage(String message) {
System.out.println("租客收到信息:" + message);
}
}
public class Landlord extends Person{
public Landlord(String name, Mediator mediator) {
super(name, mediator);
}
//与中介者进行通信
public void contact(String message) {
mediator.chat(this, message);
}
@Override
public void sendMessage(String message) {
System.out.println("包租婆收到信息:" + message);
}
}
然后我们构建一个抽象中介者
public interface Mediator {
//将信息message发送给person
void chat(Person person,String message);
}
具体中介者
public class MediatorStructure implements Mediator {
Landlord landlord;
Tenant tenant;
public void setLandlord(Landlord landlord) {
this.landlord = landlord;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
@Override
public void chat(Person person, String message) {
if(person.getName().equals("租客")) {
landlord.sendMessage(message);
}else if(person.getName().equals("包租婆")){
tenant.sendMessage(message);
}
}
}
测试一下
public class Client {
public static void main(String[] args) {
MediatorStructure mediatorStructure = new MediatorStructure();
Tenant tenant = new Tenant("租客", mediatorStructure);
Landlord landlord = new Landlord("包租婆", mediatorStructure);
mediatorStructure.setLandlord(landlord);
mediatorStructure.setTenant(tenant);
landlord.contact("快交水电费!!");
tenant.contact("一共多少钱啊");
landlord.contact("200元,请尽快交齐");
tenant.contact("好的");
}
}
租客收到信息:快交水电费!!
包租婆收到信息:一共多少钱啊
租客收到信息:200元,请尽快交齐
包租婆收到信息:好的
优点
- 简化了对象之间的关系,将系统的各个对象之间的相互关系进行封装,将各个同事类解耦,使系统成为松耦合系统。
- 减少了子类的生成。
- 可以减少各同事类的设计与实现。
缺点
由于中介者对象封装了系统中对象之间的相互关系,导致其变得非常复杂,使得系统维护比较困难。
备忘录模式(Memento Pattern)
备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式,其别名为Token。
由于备忘录模式难以理解,我们先来介绍一下它的结构
结构
- Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。
- Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。
- Caretaker(负责人):负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。
相信大家都下过棋吧,我们今天来用备忘录模式模拟一下下棋与悔棋操作。
示例
棋子类 Chessman,原发器角色
@Data
@AllArgsConstructor
class Chessman {
private String label;
private int x;
private int y;
//保存状态
public ChessmanMemento save() {
return new ChessmanMemento(this.label, this.x, this.y);
}
//恢复状态
public void restore(ChessmanMemento memento) {
this.label = memento.getLabel();
this.x = memento.getX();
this.y = memento.getY();
}
public void show() {
System.out.println(String.format("棋子<%s>:当前位置为:<%d, %d>", this.getLabel(), this.getX(), this.getY()));
}
}
备忘录角色 ChessmanMemento
@Data
@AllArgsConstructor
class ChessmanMemento {
private String label;
private int x;
private int y;
}
负责人角色 MementoCaretaker
class MementoCaretaker {
//定义一个集合来存储备忘录
private ArrayList mementolist = new ArrayList();
public ChessmanMemento getMemento(int i) {
return (ChessmanMemento) mementolist.get(i);
}
public void addMemento(ChessmanMemento memento) {
mementolist.add(memento);
}
}
棋子客户端,维护了一个 MementoCaretaker 对象
class Client {
private static int index = -1;
private static MementoCaretaker mc = new MementoCaretaker();
public static void main(String args[]) {
Chessman chess = new Chessman("车", 1, 1);
play(chess);
chess.setY(4);
play(chess);
chess.setX(5);
play(chess);
undo(chess, index);
undo(chess, index);
redo(chess, index);
redo(chess, index);
}
//下棋,同时保存备忘录
public static void play(Chessman chess) {
mc.addMemento(chess.save());
index++;
chess.show();
}
//悔棋,撤销到上一个备忘录
public static void undo(Chessman chess, int i) {
System.out.println("******悔棋******");
index--;
chess.restore(mc.getMemento(i - 1));
chess.show();
}
//撤销悔棋,恢复到下一个备忘录
public static void redo(Chessman chess, int i) {
System.out.println("******撤销悔棋******");
index++;
chess.restore(mc.getMemento(i + 1));
chess.show();
}
}
输出如下
棋子<车>:当前位置为:<1, 1>
棋子<车>:当前位置为:<1, 4>
棋子<车>:当前位置为:<5, 4>
悔棋
棋子<车>:当前位置为:<1, 4>
悔棋
棋子<车>:当前位置为:<1, 1>
撤销悔棋
棋子<车>:当前位置为:<1, 4>
撤销悔棋
棋子<车>:当前位置为:<5, 4>
优点
- 它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。
- 备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。
缺点
资源消耗过大,如果需要保存的原发器类的成员变量太多,就不可避免需要占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源。
我的下一篇设计模式博客:设计模式详解(6) – 行为型模式详解下
参考: https://blog.csdn.net/liaodehong/article/details/51408076
https://blog.csdn.net/lilu_leo/article/details/7609496
https://blog.csdn.net/zhengzhb/article/details/7610745#commentBox
https://www.cnblogs.com/chenssy/p/3348520.html