第二部分:23种设计模式(中)
8、中介者模式
中介者模式的定义:用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
中介者模式的通用类图如下所示:
中介者模式由以下几部分组成:
1)Mediator抽象中介者角色
抽象中介者角色定义统一的接口,用于各同事角色之间的通信。
2)Concrete Mediator具体中介者角色
具体中介者角色通过协调个同事角色实现协作行为,因此它必须依赖于各个同事角色
3)Colleague同事角色
每一个同事角色都知道中介者角色,而且与其他的同时角色通信的时候,一定要通过中介者角色协作。每个同事类的行为分为两种:一种是同时本身的行为,比如改变对象本身的状态,处理自己的行为等,这种行为叫做自发行为(Self-Method),与其他的同事类或中介者没有任何的依赖;第二种是必须依赖中介者才能完成的行为,叫做依赖方法(Dep-Method)。
中介者模式的通用源码如下:
代码清单:通用抽象中介者
public abstract class Mediator{
//定义同事类
protected ConcreteColleague1 c1;
protected ConcreteColleague2 c2;
//通过getter/setter方法把同事类注入进来
public ConcreteColleague1 getC1(){
return c1;
}
public void setC1(ConcreteColleague1 c1){
this.c1 = c1;
}
public ConcreteColleague2 getC2(){
return c2;
}
public void setC2(ConcreteColleague2 c2){
this.c2 = c2;
}
//中介者模式的业务逻辑
public abstract void doSomething1();
public abstract void doSomething2();
}
代码清单:通用中介者
public class ConcreteMediator extends Mediator{
@Override
public void doSomething1(){
//调用同事类的方法,只要是public方法都可以调用
super.c1.selfMethod1();
super.c2.selfMethod2();
}
public void doSomething2(){
super.c1.selfMethod1();
super.c2.selfMethod2();
}
}
注:中介者所具有的方法doSomething1和doSomething2都是比较复杂的业务逻辑,为同事类服务,其实现是依赖各个同事类来完成的。
代码清单:抽象同事类
public abstract class Colleague{
protected Mediator mediator;
public Colleague(Mediator _mediator){
this.mediator = _mediator;
}
}
代码清单:具体抽象类
public class ConcreteColleague1 extends Colleague{
//通过构造函数传递中介者
public ConcreteColleague1(Mediator _mediator){
super(_mediator);
}
//自有方法 self-method
public void selfMethod1(){
//处理自己的业务逻辑
}
//依赖方法 dep-method
public void depMethod1(){
//处理自己的业务逻辑
//自己不能处理的业务逻辑,委托给中介者处理
super.mediator.doSomething1();
}
}
public class ConcreteColleague2 extends Colleague{
//通过构造函数传递中介者
public ConcreteColleague2(Mediator _mediator){
super(_mediator);
}
//自有方法 self-method
public void selfMethod2(){
//处理自己的业务逻辑
}
//依赖方法 dep-method
public void depMethod2(){
//处理自己的业务逻辑
//自己不能处理的业务逻辑,委托给中介者处理
super.mediator.doSomething2();
}
}
注:为什么同事类要使用构造函数注入中介者,而中介者使用getter/setter方式注入同事类呢?这是因为同事类必须有中介者,而中介者却可以只有部分同事类。
中介者模式的优点:
中介者模式的优点及时减少了类间依赖,把原有的一对多的依赖变成了一对一的依赖,同事类只依赖中介者,减少了依赖,当然同时也降低了类间的耦合。
中介者模式的缺点:
中介者模式的缺点就是中介者会膨胀得很大,而且逻辑复杂,原本N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂。
9、命令模式
命令模式的定义:讲一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
命令模式的通用类图如下:
命令模式是一个高内聚的模式,在类图中我们看到有三个角色:
1)Receive接收者角色
该角色就是干活的角色,命令传递到这里是应该被执行的
2)Command命令角色
需要执行的所有角色都在这里声明
3)Invoker调用者角色
接收到命令,并执行角色
命令模式通用源码如下:
代码清单:通用Receiver类
public abstract class Receiver{
//抽象接收者,定义每个接收者都必须完成的业务
public abstract void doSomething();
}
代码清单:具体的Receiver类
public class ConcreteReciver1 extends Receiver{
//每个接收者都必须处理一定的业务逻辑
public void doSomething(){
}
}
public class ConcreteReciver2 extends Receiver{
//每个接收者都必须处理一定的业务逻辑
public void doSomething(){
}
}
注:接收者可以是N个,依赖业务的具体定义。命令角色是命令模式的核心
代码清单:抽象的Command类
public abstract class Command{
//每个命令类都必须有一个执行命令的方法
public abstract void execute();
}
注:根据环境需求,具体的命令类也可以有N个
代码清单:具体的Command类
public class ConcreteCommand1 extends Command{
//对哪个Receiver类进行命令处理
private Receiver receiver;
//构造函数传递接收者
public ConcreteCommand1(Receiver _receiver){
this.receiver = _receiver;
}
//必须实现一个命令
public void execute(){
//业务处理
this.receiver.doSomething();
}
}
public class ConcreteCommand2 extends Command{
//对哪个Receiver类进行命令处理
private Receiver receiver;
//构造函数传递接收者
public ConcreteCommand2(Receiver _receiver){
this.receiver = _receiver;
}
//必须实现一个命令
public void execute(){
//业务处理
this.receiver.doSomething();
}
}
代码清单:调用者Invoker类
public class Invoker{
private Command command;
//受气包,接受命令
private void setCommand(Command _command){
this.command = _command;
}
//执行命令
public void action(){
this.command.execute();
}
}
代码清单:场景类
public class Client{
public static void amin(String[] args){
//首先声明调用者Invoker
Invoker invoker = new Invoker();
//定义接收者
Receiver receiver = new ConcreteReciver1();
//定义一个发送给接收者的命令
Command command = new ConcreteCommand1(receiver);
//把命令交给调用者去执行
invoker.setCommand(command);
invoker.action();
}
}
命令模式的优点:
1)类间解耦
调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需调用Command抽象类的execute方法就可以,不需要了解到底是哪个接收者执行。
2)可扩展性
Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合。
3)命令模式结合其他模式会更优秀
命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少Command子类的膨胀问题。
10、责任链模式
责任链模式的定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,知道有对象处理它为止。
责任链模式的通用类图如下:
责任链模式的核心是在“链”上,“链”是由多个处理者ConcreteHandle组成的
责任链模式通用源代码如下:
代码清单:抽象处理者
public abstract class Handler{
private Handler nextHandler;
//每个处理者都必须对请求做出处理
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;
}
//设置下一个处理者是谁
public void setNext(Handler _handler){
this.nextHandler = _handler;
}
//每个处理者都有一个处理级别
protected abstract Level getHandlerLevel();
//每个处理者都必须实现处理任务
protected abstract Response echo(Request request);
}
注:抽象的处理者实现三个职责:一是定义一个请求的处理方法handleMessage,唯一对外开放的方法;二是定义一个 链的编排方法setNext,设置下一个处理者;三是定义了具体的请求者必须实现的两个方法:定义自己能够处理的级别getHandlerLevel和具体的处理任务echo。
代码清单:具体处理者
public class ConcreteHandler1 extends Handler{
//定义自己的处理逻辑
protected Response echo(Request request){
//完成处理逻辑
return null;
}
//设置自己的处理级别
protected Level getHandlerLevel(){
//设置自己的处理级别
return null;
}
}
public class ConcreteHandler2 extends Handler{
//定义自己的处理逻辑
protected Response echo(Request request){
//完成处理逻辑
return null;
}
//设置自己的处理级别
protected Level getHandlerLeve2(){
//设置自己的处理级别
return null;
}
}
public class ConcreteHandler3 extends Handler{
//定义自己的处理逻辑
protected Response echo(Request request){
//完成处理逻辑
return null;
}
//设置自己的处理级别
protected Level getHandlerLeve3(){
//设置自己的处理级别
return null;
}
}
注:在处理者中涉及三个类:Level类负责定义请求和处理级别,Request类负责封装请求,Response负责封装链中返回的结果,该三个类都需要根据业务产生,读者可以在实际应用中完成相关的业务填充
代码清单:模式中有关框架代码
public class Level{
//定义一个请求和处理等级
}
public class Request{
//请求的等级
public Level getRequestLevel(){
return null;
}
}
public class Response{
//处理者返回的数据
}
注:在场景类或高层模块中对链进行组装,并传递请求,返回结果
代码清单:场景类
public class Client{
public static void main(String[] args){
//声明所有的处理节点
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
Handler handler3 = new ConcreteHandler3();
//设置链中的阶段顺序1--》2--3
handler1.setNext(handler2);
handler2.setNext(handler3);
//提交请求,返回结果
Response response = handler1.handlerMessage(new Request());
}
}
责任链模式的优点:
责任链模式非常显著的优点是将请求和处理分开。请求者不用知道是谁处理的,处理者不用知道请求的全貌(例如在J2EE项目开发中,可以剥离出无状态Bean由责任链处理)两者解耦,提高系统的灵活性。
责任链模式的缺点:
1)性能问题
每个请求都是从链头遍历到链尾,特别是链比较长的时候,性能是一个非常大的问题。
2)调试很不方便
特别是链条比较长,环节比较多的时候,由于采用了类似递归的方式,调试的时候逻辑可能比较复杂。
注:链中节点数量需要控制,避免出现超长链的情况,一般的做法是在Handler中设置一个最大节点数量,在setNext方法中判断是否已经是超过其阈值,超过则不允许该链简历,避免无意识地破坏系统性能。
11、装饰模式
装饰模式的定义:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。
装饰模式的通用类图如下:
在类图中,有四个角色需要说明:
1)Component抽象构件
Component是一个接口或者是抽象类,就是定义我们最核心的对象,也就是最原始的对象
注:在装饰模式中,必然有一个最基本、最核心、最原始的接口或抽象类充当Component抽象构件
2)ConcreteComponent具体构件
ConcreteComponent是最核心、最原始、最基本的接口或抽象类的实现,你要装饰的就是它。
3)Decorator装饰角色
一般是一个抽象类,实现接口或抽象方法,里面不一定有抽象方法,属性里必然有一个private变量指向Component抽象构件。
4)具体装饰角色
ConcreteDecoratorA和ConcreteDecoratorB是两个具体的装饰类,你要把你最核心的、最原始的、最基本的东西装饰成其他东西。
装饰模式的通用源码如下:
代码清单:抽象构件
public abstract class Component{
//抽象的方法
public abstract void operate();
}
代码清单:具体构件
public class ConcreteComponent extends Component{
//具体实现
@Override
public void operate(){
System.out.println("do Something");
}
}
代码清单:抽象装饰者
public abstract class Decorator extends Component{
private Component component = null;
//通过构造函数传递被修饰者
public Decorator(Component _component){
this.component = _component;
}
//委托给被修饰者执行
@Override
public void operate(){
this.component.operate();
}
}
代码清单:具体的装饰类
public class ConcreteDecorator1 extends Decorator{
//定义被修饰者
public ConcreteDecorator1(Component _component){
super(_component);
}
//定义自己的修饰方法
private void method1(){
System.out.println("method1 修饰");
}
//重写父类的Operation方法
public void operate(){
this.method1();
super.operate();
}
}
public class ConcreteDecorator2 extends Decorator{
//定义被修饰者
public ConcreteDecorator2(Component _component){
super(_component);
}
//定义自己的修饰方法
private void method2(){
System.out.println("method1 修饰");
}
//重写父类的Operation方法
public void operate(){
this.method2();
super.operate();
}
}
代码清单:场景类
public class Client{
public static void main(String[] args){
Component component = new ConcreteComponent();
//第一次修饰
component = new ConcreteDecorator1(component);
//第二次修饰
component = new ConcreteDecorator2(component);
//修饰后运行
component.operate();
}
}
装饰模式的优点:
1)装饰类和被装饰类可以独立发展,而不会相互耦合。换句话说,Component类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的构件。
2)装饰模式是继承关系的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返回的对象还是Component,实现的还是is-a的关系。
3)装饰模式可以动态地扩展一个实现类的功能。
装饰模式的缺点:
多层的装饰是复杂的,尽量减少装饰类的数量,以便降低系统的复杂度。
装饰模式的使用场景:
1)需要扩展一个类的功能,给一个类增加附加功能。
2)需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
3)需要为一批的兄弟类进行改装或加装功能,当然是首选装饰模式。
12、策略模式
策略模式的定义:也叫政策模式,定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。
策略模式通用类图如下:
策略模式使用的就是面向对象的继承和多态机制,我们来看策略模式的三个角色:
1)Context封装角色
它也叫做上下文角色,起承上启下封装作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。
2)Strategy抽象策略角色
策略、算法家族的抽象,通常为借口,定义每个策略或算法必须具有的方法和属性。
3)ConcreteStrategy具体策略角色
实现抽象策略中的操作,该类含有具体的算法
策略模式的通用源码如下:
代码清单:抽象的策略角色
public interface Strategy{
//策略模式的运算法则
public void doSomething();
}
注:具体策略也是非常普通的一个实现类,只要实现接口中的方法就可以
代码清单:具体策略角色
public class ConcreteStrategy1 implements Strategy{
public void doSomething(){
System.out.println("具体策略1的运算法则");
}
}
public class ConcreteStrategy2 implements Strategy{
public void doSomething(){
System.out.println("具体策略1的运算法则");
}
}
注:策略模式的重点就是封装角色,它是借用了代理模式的思路,差别就是策略模式的封装角色和被封装的策略类不用是同一个接口
代码清单:封装角色
publi class Context{
//抽象策略
private Strategy strategy = null;
//构造函数设置具体策略
public Context(Strategy _strategy){
this.strategy = _strategy;
}
//封装后的策略方法
public void doAnything(){
this.strategy.doSomething();
}
}
代码清单:高层模块
public class Client{
public static void main(String[] args){
//声明一个具体的策略
Strategy strategy = new ConcreteStrategy1();
//声明上下文对象
Context context = new Context(strategy);
//执行封装后的方法
context.doAnything();
}
}
策略模式的优点:
1)算法可以自由切换
2)避免使用多重条件判断
3)扩展性良好
策略模式的缺点:
1)策略类数量增多
2)所有的策略类都需要对外暴露
策略模式的使用场景:
1)多个类只有在算法或行为上稍有不同的场景
2)算法需要自由切换的场景。
3)需要屏蔽算法规则的场景
注:如果系统中的一个策略家族的具体策略数量超过4个,则需要考虑使用混合模式,解决策略类膨胀和对外暴露的问题。
13、适配器模式
适配器模式的定义:将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
适配器模式幼教变压器模式,也叫包装模式(Wrapper)。
适配器模式通用类图如下:
来看一下适配器模式的三个角色:
1)Target目标角色
该角色定义把其他类转换成何种接口,也就是我们的期望接口
2)Adaptee源角色
逆向把谁转换成目标角色,这个“谁”就是源角色,它是已经存在的、运行良好的类或对象,经过适配器角色的包装,它会成为一个崭新、靓丽的角色。
3)Adapter适配器角色
适配器模式的核心角色,其它两个角色都是已经存在角色,而适配器角色是需要新建立的,它的职责非常简单:把源角色转换为目标角色,通过继承或是类关联的方式。
适配器模式通用源代码如下:
代码清单:目标角色
public interface Target{
//目标角色有自己的方法
public void request();
}
代码清单:目标角色的实现类
public class ConcreteTarget implements Target{
public void request(){
System.out.prinln("if you need any help,pls call me!");
}
代码清单:源角色
public class Adaptee{
//原有的业务逻辑
public void doSomething(){
System.out.println("I'm kind of busy,leave me alone,pls!");
}
}
代码清单:适配器角色
public class Adapter extends Adaptee implements Target{
public void request(){
super.doSomething();
}
}
代码清单:场景类
public class Client{
public static void main(String[] args){
//原有的业务逻辑
Target target = new ConcreteTarget();
target.request();
//现在增加了适配器角色后的业务逻辑
Target target2 = new Adapter();
target2.request();
}
}
适配器模式的优点:
1)适配器模式可以让两个没有任何关系的类在一起运行,只要适配器这个角色能搞定他们就成。
2)增加了类的透明性
3)提高了类的复用度
4)灵活性非常好
注:适配器模式是一个补偿模式,或者说是一个“补救”模式,通常用来解决接口不相容的问题,它通过把非本系统接口的对象包装成本系统可以接受的对象,从而简化了系统大规模变更风险的存在。
14、迭代器模式
迭代器模式的定义:它提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。
迭代器模式的通用类图如下:
来看迭代器模式中各个角色:
1)Iterator抽象迭代器
抽象迭代器负责定义访问和遍历元素的接口,而且基本上是有固定的3个方法:first()获得第一个元素,next()访问下一个元素,isDone()是否已经访问到底部(Java叫做hasNext()方法)。
2)ConcreteIterator具体迭代器
具体迭代器角色要实现迭代器几口,完成容器元素的遍历
3)Aggregate抽象容器
容器角色负责提供创建具体迭代器角色的接口,必然提供一个类似createIterator()这样的方法,在Java中一般是iterator()方法。
4)ConcreteAggregate具体容器
具体容器实现容器接口定义的方法,创建出容纳迭代器的对象。
注:简单地说,迭代器就类似于一个数据库中的油表,可以在一个容器内上下翻滚,遍历所有它需要查看的元素。
15、组合模式
组合模式的定义:将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
组合模式的通用类图如下:
组合模式也叫合成模式,有时又叫做部分-整体模式(Part-Whole),主要是用来描述部分与整体的关系。
来看看组合模式的几个角色:
1)Component抽象构件角色
定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性。
2)Leaf叶子构件
叶子对象,其下再没有其他的分支,也就是遍历的最小单位。
3)Composite树枝构件
树枝对象,它的作用是组合树枝节点和叶子节点形成一个树形结构。
组合模式的通用源代码如下所示:
代码清单:抽象构件
public abstract class Component{
//个体和整体都具有的共享
public void doSomething(){
//编写业务逻辑
}
}
代码清单:树枝构件
public class Composite extends Component{
//构件容器
private ArrayList<Component> componentArrayList = new ArrayList<Component>();
//增加一个叶子节点或树枝构件
public void add(Component component){
this.componentArrayList.add(component);
}
//删除一个叶子节点或树枝构件
public void remove(Component component){
this.componentArrayList.remove(component);
}
//获得分支下的所有叶子构件和树枝构件
public ArrayList<Component> getChildren(){
return this.componentArrayList;
}
}
注:树叶节点是没有子下级对象的对象,定义参加组合的原始对象行为
代码清单:树叶构件
public class Leaf extends Component{
/*
*可以覆写父类方法
*public void doSomething(){
*
*}
*/
}
代码清单:场景类
public class Client{
public static void main(String[] args){
//创建一个根结点
Composite root = new Composite();
root.doSomething();
//创建一个树枝构件
Composite branch = new Composite();
//创建一个叶子节点
Leaf leaf = new Leaf();
//建立整体
root.add(branch);
branch.add(leaf);
}
//通过递归遍历树
public static void display(Composite root){
for(Component c:root.getChildren()){
if(c instanceof Leaf){//叶子节点
c.doSomething();
}else{//树枝节点
display((Composite)c);
}
}
}
}
组合模式的优点:
1)高层模块调用简单
2)节点自由增加
组合模式的使用场景:
1)维护和展示部分-整体关系的场景,如树形菜单、文件和文件夹管理。
2)从一个整体中能够独立出部分模块或功能的场景。
注:只要是树形结构,就要考虑使用组合模式。
16、观察者模式
观察者模式的定义:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
观察者模式的通用类图如下:
观察者模式(Observer Pattern)也叫发布订阅模式(Publish/subscribe),来看一下它的几个角色:
1)Subject被观察者
定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责:观察观察者并通知观察者。
2)Observer观察者
观察者接收到消息后,即进行update(更新方法)操作,对接收到的信息进行处理。
3)ConcreteSubject具体的被观察者
定义被观察者自己的业务逻辑,同时定义对哪些时间进行通知。
4)ConcreteObserver具体的观察者
每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑。
观察者模式的通用源码如下:
代码清单:被观察者
public abstract class Subject{
//定义一个观察者数组
private Vector<Observer> obsVector = new Vector<Observer>();
//增加一个观察者
public void addObserver(Observer o){
this.ovsVector.add(o);
}
//删除一个观察者
public void delObserver(Observer o){
this.obsVector.remove(o);
}
//通知所有观察者
public void notifyObservers(){
for(Observer o:this.obsVector){
o.update();
}
}
}
代码清单:具体被观察者
public class ConcreteSubject extends Subject{
//具体的业务
public void doSomething(){
/*
*do something
*/
super.notifyObservers();
}
}
代码清单:观察者
public interface Observer{
//更新方法
public void update();
}
注:观察者一般是一个接口,每一个实现该接口的实现类都是具体观察者
代码清单:具体观察者
public class ConcreteObserver implements Observer{
//实现更新方法
public void update(){
System.out.println("接收到消息,并进行处理!");
}
}
代码清单:场景类
public class Client{
public static void main(String[] args){
//创建一个被观察者
ConcreteSubject subject = new ConcreteSubject();
//定义一个观察者
Observer obs = new ConcreteObserver();
//观察者观察被观察者
subject.addObserver(obs);
//观察者开始活动了
subject.doSomething();
}
}
观察者模式的优点:
1)观察者和被观察者之间是抽象耦合
2)建立一套触发机制
观察者模式的使用场景:
1)关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。
2)事件多级触发场景
3)跨系统的消息交换场景,如消息队列的处理机制。
注:
上一篇:23种设计模式(上)
下一篇:23种设计模式(下)
推荐阅读:秦小波著《设计模式之禅》