总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
其实还有两类:并发型模式和线程池模式。
先来张图,看看这11中模式的关系:
一、父类与子类关系
1、策略模式(strategy)
策略模式:定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数,关系图如下:
抽象的策略角色
策略、算法家族的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性,algorithm是“运算法则”的意思。
public interface Strategy {
// 策略模式的运算法则
void doSomething();
}
具体策略角色
public class ConcreteStrategy implements Strategy {
@Override
public void doSomething() {
// 业务逻辑
}
}
封装角色
也叫做上下文角色,起承上启下封装作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在变化。
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
// 封装后的策略方法
public void doAnything() {
this.strategy.doSomething();
}
}
场景
@Test
public void client() {
Strategy strategy = new ConcreteStrategy();
Context context = new Context(strategy);
context.doAnything();
}
优点:
1).算法可以自由切换
2).避免使用多重条件判断
3).扩展性好
缼点:
1).略类数量增多,每一个策略都是一个类,复用的可能性很小,类数量增多
2).所有的策略类都需要对外暴露,这与迪米特法则相违背的
注意:如果系统中的策略数量超过4个,则需要考虑使用混合模式。在实际项目中,一般通过工厂方法模式来实现策略类的声明。
2、模板方法模式(Template Method)
模板方法模式:定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。通用类图如下:
模板方法模式非常简单,仅仅用了Java的继承机制。下面将详细介绍模板方法和基本方法:
1. 模板方法
可以有一个或几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑。由于模板方法是具体方法,因此模板方法模式中的抽象层只能是抽象类,而不是接口。
2. 基本方法
基本方法是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:。
1).抽象方法:一个抽象方法由抽象类声明、由其具体子类实现。在C#语言里一个抽象方法以abstract关键字标识。
2).具体方法:一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
3). 钩子方法:一个钩子方法由一个抽象类或具体类声明并实现,而其子类可能会加以扩展。通常在父类中给出的实现是一个空实现(可使用virtual关键字将其定义为虚函数),并以该空实现作为方法的默认实现,当然钩子方法也可以提供一个非空的默认实现。
在模板方法模式中,钩子方法有两类:第一类钩子方法可以与一些具体步骤“挂钩”,以实现在不同条件下执行模板方法中的不同步骤,这类钩子方法的返回类型通常是bool类型的,这类方法名一般为IsXXX(),用于对某个条件进行判断,如果条件满足则执行某一步骤,否则将不执行,如下代码片段所示:
//模板方法
public final void templateMethod() {
// 处理其他业务逻辑
//通过钩子方法来确定某步骤是否执行
if (IsPrint()) {
// 执行逻辑
}
}
//钩子方法
public boolean IsPrint() {
return true;
}
在代码中IsPrint()方法即是钩子方法,它可以决定Print()方法是否执行,一般情况下,钩子方法的返回值为true,如果不希望某方法执行,可以在其子类中覆盖钩子方法,将其返回值改为false即可,这种类型的钩子方法可以控制方法的执行,对一个算法进行约束。
还有一类钩子方法就是实现体为空的具体方法,子类可以根据需要覆盖或者继承这些钩子方法,与抽象方法相比,这类钩子方法的好处在于子类如果没有覆盖父类中定义的钩子方法,编译可以正常通过,但是如果没有覆盖父类中声明的抽象方法,编译将报错。
在模板方法模式中,抽象类的典型代码如下:
//模板方法
public final void templateMethod() {
primitiveOperation1();
primitiveOperation2();
primitiveOperation3();
}
//基本方法—具体方法
public void primitiveOperation1() {
//实现代码
}
//基本方法—抽象方法
public abstract void primitiveOperation2();
//基本方法—钩子方法
public void primitiveOperation3() {
}
抽象模板类
在抽象类中定义了一系列基本操作(PrimitiveOperations),这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。同时,在抽象类中实现了一个模板方法(Template Method),用于定义一个算法的框架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法。
public abstract class AbstractClass {
// 基本方法, 尽量设计为protected类型,符合迪米特法则
protected abstract void doSomething();
// 模板方法, 为了防止恶意的操作,一般模板方法都加上final,不允许被覆写
public final void templateMethod() {
this.doAnything();
this.doSomething();
}
// 共有的逻辑
private void doAnything() {
System.out.println("AbstractClass doAnything");
}
}
具体模板类:
它是抽象类的子类,用于实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。
public class ConcreteClass extends AbstractClass{
// 实现基本方法
@Override
protected void doSomething() {
// 业务逻辑处理
System.out.println("ConcreteClass doSomething");
}
}
场景类
@Test
public void client() {
AbstractClass abstractClass = new ConcreteClass();
abstractClass.templateMethod();
}
优点:
1).封装不可变部分,扩展可变部分
2).提取公共部分代码,便于维护
3).行为由父类控制,子类实现
缼点:
一般来说,抽象类负责声明最抽象、最一般的事物属性和方法,实现类完成 具体的事物属性和方法。模板方法模式颠倒了,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响父类的结果,也就是子类对父类产生了影响,这在复杂的项目中,会带来代码阅读的难度,而且会让亲手产生不适感
二、类之间的关系(不涉及到继承)
3、观察者模式(Observer)
观察者模式也做发布订阅模式(Publish/subscribe),在项目中常使用的模式。定义:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖它的对象都会得到通知并被自动更新。
很好理解,类似于邮件订阅和RSS订阅,当我们浏览一些博客或wiki时,经常会看到RSS图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。其实,简单来讲就一句话:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系。先来看看关系图:
Subject被观察者
定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或是实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者
public abstract class Subject {
// 用Vector就是因为线程同步
private Vector<Observer> vector = new Vector<Observer>();
/*增加观察者*/
public void addObserver(Observer observer){
this.vector.add(observer);
}
/*删除观察者*/
public void delObserver(Observer observer) {
this.vector.remove(observer);
}
/*通知所有的观察者*/
public void notifyObservers(){
for (Observer observer : vector) {
observer.update();
}
}
/*自身的操作*/
public void operation(){
}
}
具体的被观察者
public class ConcreteSubject extends Subject{
// 具体的业务
public void doSomething() {
super.notifyObservers();
}
}
观察者
观察者接收到消息后,即进行update操作,对接收到的信息进行处理
public interface Observer {
void update();
}
具体的观察者
public class ConcreteObserver implements Observer{
@Override
public void update() {
System.out.println("收到信息");
}
}
场景
@org.junit.Test
public void client() {
// 创建一个被观察者
ConcreteSubject subject = new ConcreteSubject();
// 定义一个观察者
Observer observer = new ConcreteObserver();
// 观察者观察被观察者
subject.addObserver(observer);
// 观察者开始活动了
subject.doSomething();
}
优点:
1).观察者与被观察者之间是抽象耦合,易扩展。
2).完美实现链条形式。
缼点:
1).开发和调试比较复杂,消息要用异步
2).多级触发时的效率不是很高,应避免
注意事项:
1).广播链的问题
如果A触发B, B触发C,C触发D…这就太复杂了。根据经验,在一个观察者模式中最多出现一个对象既是观察者又是被观察者,也就是说消息最多被转发一次(传递两次)。
注意:它和责任链的最大区别是观察者广播链在传播的过程中消息是随时更改的,它是由相邻的两个节点协商的消息结构;
而责任链模式在消息传递过程中基本上保持消息不可变,如果要改变,也只是在原有的消息上进行修正。
2).异步处理问题
被观察者发生动作了,观察者要做出回应,如果观察者比较多,而且处理时间长怎么办?用异步,异步处理要考虑线程安全和队列的问题,可以看看Message Queue.
4.迭代器模式
迭代器模式(Iterator Pattern): 提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。目前已经是一个没落的模式,基本上没有会单独写一个,除非是产品性质的开发。如果是Java开发,尽量不要自己写迭代,java提供的Iterator一般就能满足我们的要求了。
5、责任链模式(Chain of Responsibility)
责任链模式:使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
责任链模式的重点是在“链”上(“链”是由多个处理者ConcreteHandler组成的),由一条链去处理相似的请求在链中决定谁来处理这个请求,并返回相应的结果。
有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整。先看看类图:
抽象处理者
public abstract class Handler {
private Handler nextHandler;
// 这里一个模板方法,用final方法修饰
public final Response handleMessage(Request request) {
Response response = null;
if (this.getHandlerLevel().equals(request.getRequestLevel())) {
response = this.echo(request);
} else { // 不属于自己的处理级别
// 判断是否有下一个处理者
if (this.nextHandler != null) {
response = this.nextHandler.handleMessage(request);
} else {
// 没有适当的处理者,业务自行处理
}
}
return response;
}
// 2.设置下一个处理者是谁
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
// 3.每一个处理都有一个处理级别
protected abstract Level getHandlerLevel();
// 每个处理者都必须实现处理任务
protected abstract Response echo(Request request);
}
具体处理者
// 具体处理者可以有多个,都要实现Handler
public class ConcreteHandler1 extends Handler{
// 定义自己的处理逻辑
@Override
protected Level getHandlerLevel() {
// 完成处理逻辑
return null;
}
// 定义自己的处理级别
@Override
protected Response echo(Request request) {
// 设置自己的处理级别
return null;
}
}
public class ConcreteHandler2 extends Handler{
@Override
protected Level getHandlerLevel() {
return null;
}
@Override
protected Response echo(Request request) {
return null;
}
}
模式中有关框架代码
public class Level {
// 定义一个请求和处理等级
}
public class Request {
// 请求的等级
public Level getRequestLevel() {
return null;
}
}
public class Response {
// 处理者返回的数据
}
测试
@org.junit.Test
public void client() {
// 声明所有的处理节点
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
// 设置链中的阶段顺序
handler1.setNextHandler(handler2);
// 提交请求,返回结果
Response response = handler1.handleMessage(new Request());
}
在实际应用中,一般会有一个封装类对责任模式进行封装,也就是替代测试方法,直接返回链中的第一个处理者,具体链的设置不需要高层次模块关系,这样,简化了高层次模块的调用,减少模块间的耦合,提高系统的灵活性。
此处强调一点就是,链接上的请求可以是一条链,可以是一个树,还可以是一个环,模式本身不约束这个,需要我们自己去实现,同时,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象。
应用模式:如第一个请求是人民币,美元,日元。。。
优点:
责任链模式非常显著的优点是将请求和处理分开,请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌,两者解耦,提高系统的灵活性。
缼点:
1).性能问题,每个请求都是从链头遍历到链尾,特别是在链比较长的时候,性能是一个非常大的问题。
2).调试不很方便,特别是链条比较长,环节比较多的时候,由于采用了类似递归的方式,调试的时候逻辑可能比较复杂
6、命令模式(Command)
命令模式是一个高内聚的模式,其定义为:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
很好理解,
举个例子,司令员下令让士兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经过传递,传到了士兵耳朵里,士兵去执行。这个过程好在,三者相互解耦,任何一方都不用去依赖其他人,只需要做好自己的事儿就行,司令员要的是结果,不会去关注到底士兵是怎么实现的。我们看看关系图(在点类似三层架构):
Receive接收者角色:该用色就是干活的角色,命令传递到这里是应该被执行的。
Command调用者角色:需要执行的所有命令都在这里声明。
Invoker调用者角色:接收命令并执行命令。
命令模式比较简单,但在项目中使用很多,因为它的封装性非常好,把请求访求(Invoker)和执行方(Receiver)分开了,扩展性也有很好的保障,通用代码如下。
通用的Receiver类
之所以是抽象类,是因为接收者可以有多个,有多个就需要定义一个所有特性的抽象集合。
public abstract class Receiver {
// 抽象接收者,定义每个接收者都必须完成的业务
public abstract void doSomething();
}
具体的Receiver类
public class ConcreteReceiver extends Receiver {
// 每个接收者都必须处理一定的业务逻辑
@Override
public void doSomething() {
}
}
抽象的Command类
public abstract class Command {
// 每个接收者都必须有一个执行命令的方法
public abstract void execute();
}
具体的Command类
public class ConcreteCommand extends Command {
// 对哪个Receiver类进行命令处理
private Receiver receiver;
// 构造函数传递接收者
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
// 必须实现一个命令
@Override
public void execute() {
// 业务处理
this.receiver.doSomething();
}
}
调用者Invoker类
调用者就像是一个受气包,不管什么命令,都要接收、执行。
public class Invoker {
private Command command;
// 受气包,接受命令
public void setCommand(Command command) {
this.command = command;
}
// 执行命令
public void action() {
this.command.execute();
}
}
场景类
@Test
public void client() {
Invoker invoker = new Invoker();
Receiver receiver = new ConcreteReceiver();
Command command = new ConcreteCommand(receiver);
invoker.setCommand(command);
invoker.action();
}
优点:
1).类间解耦,调用者与接收者之间没有任何依赖关系,调用者实现功能时只需要调用Command抽象类的execute方法就可以,不需要了解到底是哪个接收者执行。
2).可扩展性:Command的子类可以非常容易扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合
3).命令模式结合其他模式会更优秀:命令模式可以结合责任链模式,实现命令族解析任务;结合模式方法模式,则可以减少Command子类的膨胀问题
缼点:
如果有N个命令,Command的子类就是N个。
使用场景:
只要你认为是命令的地方就可以采用命令模式。
注意事项:
如何命令要撤回怎么办
1.结合备忘录模式还原最后状态,该方法适合接收者为状态的变更情况,而不适合事件处理。
2.通过增加一个新的命令,实现事件的回滚。
三、类的状态
7、备忘录模式(Memento)
备忘录模式让“后悔”在程序的世界中真实可行,其定义如下:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态。通俗地说,备忘录模式是一个对象的备份模式,提供了一种程序数据的备份方法,其通用类图如下:
三个角色如下:
* Originator发起人角色:记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。
* Memento备忘录角色:负责存储Originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。
* Caretaker备忘录管理员角色:对备忘录进行管理、保存和提供备忘录。
发起人角色
public class Originator {
private String state = ""; // 内部状态
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
// 创建一个备忘录
public Memento createMemento() {
return new Memento(this.getState());
}
// 恢复一个备忘录
public void restoreMemento(Memento memento) {
this.setState(memento.getState());
}
}
备忘录角色
public class Memento {
// 发起人的内部状态
private String state = "";
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
备忘录管理员角色
public class Caretaker {
private Memento memento; // 备忘录对象
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
场景类
@Test
public void client() {
Originator originator = new Originator(); // 定义出发起人
Caretaker caretaker = new Caretaker(); // 定义出备忘录管理员
// 创建一个备忘录
caretaker.setMemento(originator.createMemento());
// 恢复一个备忘录
originator.restoreMemento(caretaker.getMemento());
}
使用场景:
1).需要保存和恢复数据的相关状态场景。
2).提供一个可回滚(rollack)的操作; 如浏览器的后退,ctrl+z等
3).需要监控的副本场景。备份一个主线程中的对象,然后由分析程序来分析
4).数据库连接的事务管理就是用的备忘录模式。
注意事项:
1).备忘录的生命期。备忘录创建出来就要在“最近”的代码中使用。
2).备忘录的性能:不要在频繁建立备份的场景中使用备忘录模式(比如一个for循环中)
扩展:
8、状态模式(State)
状态模式:当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。
状态模式的核心是封装,状态的变更引起了行为的变更,从外部看起来就好像这个对象的类发生了改变一样。
核心思想就是:当对象的状态改变时,同时改变其行为,很好理解!就拿QQ来说,有几种状态,在线、隐身、忙碌等,每个状态对应不同的操作,而且你的好友也能看到你的状态,所以,状态模式就两点:1、可以通过改变状态来获得不同的行为。2、你的好友能同时看到你的变化。其通用类图如下
抽象状态角色
接口或抽象类,负责对象状态定义,并且封装上下文角色以实现状态切换。
public abstract class State {
protected Context context; // 定义一个上下文角色,提供子类角色
public void setContext(Context context) {
this.context = context;
}
// 几个行为
public abstract void handle1();
public abstract void handle2();
}
具体状态角色
有两个职责:1.处理本状态必须完成的任务,2决定是否可以过渡到其他状态。通俗地说,就是本状态下要做的事情,以及本状态如何过渡到其他状态。
public class ConcreteState1 extends State {
@Override
public void handle1() {
// 本状态下必须处理的逻辑
}
@Override
public void handle2() {
// 设置当前状态为state2
super.context.setCurrentState(Context.STATE2);
// 过渡到state1状态,由context实现
super.context.handle2();
}
}
public class ConcreteState2 extends State {
@Override
public void handle1() {
super.context.setCurrentState(Context.STATE1);
super.context.handle1();
}
@Override
public void handle2() {
// 本状态下必须处理的逻辑
}
}
上下文角色
定义客户端需要的接口,并且负责具体状态的切换
该角色有两个约束:
1).把状态对象声明为静态常量,有几个状态对象就声明几个静态常量。
2).环境角色具有状态抽象角色定义的所有行为,具体执行使用委托方式。
public class Context {
// 定义状态
public final static State STATE1 = new ConcreteState1();
public final static State STATE2 = new ConcreteState2();
private State currentState; // 当前状态
public State getCurrentState() {
return currentState;
}
public void setCurrentState(State currentState) {
this.currentState = currentState;
// 切换状态
this.currentState.setContext(this);
}
public void handle1() {
this.currentState.handle1();
}
public void handle2() {
this.currentState.handle2();
}
}
测试类
@Test
public void client() {
Context context = new Context(); // 定义上下文角色
context.setCurrentState(new ConcreteState1()); // 初始化状态
// 行为执行
context.handle1();
context.handle2();
}
根据这个特性,状态模式在日常开发中用的挺多的,尤其是做网站的时候,我们有时希望根据对象的某一属性,区别开他们的一些功能,比如说简单的权限控制等。
使用场景:
1).结构清晰:避免了过多的switch…case或if…else语句的使用,避免了程序的复杂性,提高系统可维护性。
2).遵循设计原则:很好的体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就要增加子类,你要修改状态,你只修改一个子类就可以了。
3).封装性非常好:这是状态模式的基本要求,状态变换放置到类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换。
缺点:
只有一个缺点,就是子类会太多。解决这个问题的方式可以有:在数据库中建立一个状态表,然后根据状态执行相应的操作。
注意:
如果需要我们把已经有的几种状态按照一定的顺序再重新组装一下,那这个是建造者模式。所以建造者模式+状态模式会起到非常好的封装作用。
四、通过中间类
9、访问者模式(Visitor)
访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。—— From 百科
简单来说,访问者模式就是一种分离对象数据结构与行为的方法,通过这种分离,可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。简单关系图:
抽象元素
public abstract class Element {
public abstract void doSomething();
public abstract void accept(IVisitor iVisitor);
}
具体元素
public class ConcreteElement1 extends Element {
// 完善业务逻辑
@Override
public void doSomething() {
// 业务处理
}
// 允许哪个访问者访问
@Override
public void accept(IVisitor iVisitor) {
iVisitor.visit(this); // this是当前对象ConcreteElement1
}
}
public class ConcreteElement2 extends Element {
@Override
public void doSomething() {
}
@Override
public void accept(IVisitor iVisitor) {
iVisitor.visit(this);
}
}
抽象访问者
public interface IVisitor {
// 可以访问哪些对象
void visit(ConcreteElement1 concreteElement1);
void visit(ConcreteElement2 concreteElement2);
}
具体访问者
public class ConcreteVisitor implements IVisitor {
// 访问e1元素
@Override
public void visit(ConcreteElement1 concreteElement1) {
concreteElement1.doSomething();
}
@Override
public void visit(ConcreteElement2 concreteElement2) {
concreteElement2.doSomething();
}
}
结构对象
public class ObjectStruture {
// 对象生成器,这里通过一个工厂方法模式模拟
public static Element createElement() {
Random random = new Random();
if (random.nextInt(100) > 50) {
return new ConcreteElement1();
} else {
return new ConcreteElement2();
}
}
}
场景类
@Test
public void client() {
for (int i=0; i<10; i++) {
// 获得元素对象
Element element1 = ObjectStruture.createElement();
// 接受访问者访问
element1.accept(new ConcreteVisitor());
}
}
该模式适用场景:如果我们想为一个现有的类增加新功能,不得不考虑几个事情:1、新功能会不会与现有功能出现兼容性问题?2、以后会不会再需要添加?3、如果类不允许修改代码怎么办?面对这些问题,最好的解决方法就是使用访问者模式,访问者模式适用于数据结构相对稳定的系统,把数据结构和算法解耦,
10、中介者模式(Mediator)
中介者模式:用一个中介对象封装一系列的对象交互,中介者使各个对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
也是用来降低类类之间的耦合的,因为如果类类之间有依赖关系的话,不利于功能的拓展和维护,因为只要修改一个对象,其它关联的对象都得进行修改。如果使用中介者模式,只需关心和Mediator类的关系,具体类类之间的关系及调度交给Mediator就行,这有点像spring容器的作用。先看看图:
Mediator抽象中介者角色
定义统一的接口,用于各同事角色之间的通信。
在Mediator抽象类中我们只定义了同事类的注入,为什么使用同事实现类注入而不使用抽象类注入呢?那是因为同事类虽然有抽象,但是不有每个同事类必须要完成的业务方法,当然如果每个同事类都有相同的方法,如execute、handler等,那当然注入抽象类,做到依赖倒置。
public abstract class Mediator {
protected ConcreteColleague concreteColleague;
public ConcreteColleague getConcreteColleague() {
return concreteColleague;
}
public void setConcreteColleague(ConcreteColleague concreteColleague) {
this.concreteColleague = concreteColleague;
}
// 中介者模式的业务逻辑
public abstract void doSomething();
}
具体中介者
一般只有一个,即通用中介者。具体中介者通过协调各同事角色实现协作行为,因此它必须依赖于各个同事角色。
public class ConcreteMediator extends Mediator {
@Override
public void doSomething() {
// 调用同事类的方法,只要是public方法都可以调用
super.concreteColleague.selfMethod1();
}
}
Colleague抽象同事类
每一个同事角色都知道中介者角色,而且与其他的同事角色通信的时候,一定要通过中介者角色协作。每个同事类的行为分为两种:1.同事本身的行为,如改变对象本身的状态,处理自己的行为等,这种行为叫做自发行为(Self-Method),与其他的同事类或中介者没有任何的依赖;2.必须依赖中介者才能完成的行为,叫做依赖方法(Dep-Method)
public abstract class Colleague {
protected Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
}
具体同事类
public class ConcreteColleague extends Colleague {
// 通过构造函数传递中介者
public ConcreteColleague(Mediator mediator) {
super(mediator);
}
// 自有方法
public void selfMethod1() {
// 处理自己的业务逻辑
}
// 依赖方法
public void depMethod1() {
// 处理自己的业务逻辑
// 自己不能处理的业务逻辑,委托给中介者处理
super.mediator.doSomething();
}
}
为什么同事类要使用构造函数注入中介者,而中介者使用getter/setter方式注入同事类呢?这是因为同事类必须有中介者,而中介者却可以只有部分同事类。
优点:
减少类间的依赖,把原有的一对多的依赖变成了一对一的依赖,同事类只依赖中介者,减少了依赖,当然同时也降低了类间的耦合。
缼点:
中介者会膨胀得很大,而类的且逻辑复杂,原本N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂。
实际应用:
MVC框架,其中C就是一个中介者。在实际项目中不必使用,但产品可以。项目开发是以交付投产为目标,而产品则是以稳定、高效、扩展为宗旨。
11、解释器模式(Interpreter)
解释器模式:是一种按照规定语法进行解析的方案,一般主要应用在OOP开发中的编译器的开发中,在现在项目中使用较少,适用面比较窄。其定义:给定一门语言,定义它的方法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
解释器模式用来做各种各样的解释器,如四则运算、正则表达式等的解释器等等!