【软件构造】课件精译(十四)面向可维护性的设计模式

一、创建型模式

(1)工厂方法模式

工厂方法也称作“虚拟构造器”,当client不知道要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例时,用工厂方法。
定义一个用于创建对象的接口,让其子类来决定实例化哪一个类,从而使一个 类的实例化延迟到其子类。
在工厂方法模式下: Product p = new ConcreteTwo().makeObject();
举例
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
对于静态工厂方法,既可以在ADT 内部实现,也 可以构造单独的工厂类。
下面是一个静态工厂方法的例子:
【软件构造】课件精译(十四)面向可维护性的设计模式
好处是有名字,易于理解;不必一定要创建新对象,可以使用缓存等技术;可以返回原返回类型的任何子类型。但是,作为集中控制对象创建,一旦出现问题,则会影响整个系统;违反了开闭原则,一旦增加新的类的对象创建,则需要修改代码。
工厂方法模式
当一个类不知道它所需要的对象的类时。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可。
当一个类希望通过其子类来指定创建对象时。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和Liskov原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定。

(2)抽象工厂方法

举例一:一个用户界面工具包,能够支持不同操作系统的多种外观风格 ,如何采用单一的接口实现?
举例二:智能家居中的不同控制系统,如何设计单一的控制系统接口实现管理?
抽象工厂模式:提供接口以创建一组相关/相互依赖的对象,但不需要指明其具体类。
使用工厂返回可用于创建相关对象集的工厂。
抽象工厂模式是对工厂模式的扩展,允许一个工厂创建更多类型的对象(或者组合更多的对象构成产品),而工厂模式中一个对象只允许创建一个对象。
以下情况中,一般使用抽象工厂方法:
一个系统要独立于它的产品的创建、组合和表示时。
一个系统要由多个产品系列中的一个来配置时。
当你要强调一系列相关的产品对象的设计以便进行联合使用时。
当你提供一个产品类库,而只想显示它们的接口而不是实现时,所有产品以同样的接口出现,客户端不需要依赖具体实现。
【软件构造】课件精译(十四)面向可维护性的设计模式
看下面这个例子:
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
可以看到,签名中返回类型是接口,而实际返回的是具体实现。
【软件构造】课件精译(十四)面向可维护性的设计模式
抽象工厂方法创建的不是一个完整产品,而是“产品族”(遵循固定搭配规则的多类产品的实例),得到的结果是:多个不同产品的 object,各产品创建过程对client可见,但“搭配”不能改变。
本质上,Abstract Factory是把多类产品的factory method组合在一起。
那么如果不用 Abstract Factory,直接用多个factory method,是否能实现目的?
直接用factory method:client可能不知道搭配而用错工厂———牛仔裤+西装。
抽象工厂与工厂方法
工厂方法用来创建一个产品,但是抽象工厂创建一系列相关的产品。
抽象工厂模式使用组合将创建对象的职责委托给另一个类,而Factory Method模式使用继承并依赖派生类或子类来创建对象。
【软件构造】课件精译(十四)面向可维护性的设计模式

(3)构造器模式

【软件构造】课件精译(十四)面向可维护性的设计模式
将复杂对象的构造与其表示分开,以便相同的构建过程可以创建不同的表示 。构造器模式用来组装复杂的实例。
【软件构造】课件精译(十四)面向可维护性的设计模式
举例
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
抽象工厂与构造器模式对比
抽象工厂专注于产品族的构建,构建过程客户端可见;而构造器构建过程复杂, 复杂产品的构建会不时变化(修改Director类) ,构建过程客户端不可见。不过, 二者可以结合完成复杂产品族的构建。
另外,对于与模版模式,其目标是为了复用算法的公共结构(次序),而构造器模式的目标是“创建复杂对象”,灵活扩展。

二、结构型模式

(1)桥接模式

桥接模式将抽象概念与实现分离,避免过于复杂的继承结构。通过面向客户的逻辑继承结构和面向实现的物理继承结构,来实现两个继承结构分离,或者说,将类的功能层次结构(abstract)与实现层次结构分离(implementation)。桥接模式是OOP最基本的structural pattern,通过delegation+inheritance建立两个具体类之间的关系(DIP依赖转置,抽象依赖于抽象)。
【软件构造】课件精译(十四)面向可维护性的设计模式
举例
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
可以看到,两个类分别继承了DrawAPI,然后这个接口又在抽象类Shape中实现委托。这样,继承了Shape的Circle便可以drawCircle。这种模式实现了抽象和具体实现的分离,通过Implementor作为桥梁,也就是这个例子中Shape的DrawAPI的具体实现,将Circle与DrawAPI这个接口连接起来。
这样做有什么好处呢?我看到一篇博客感觉讲的很好,比如要绘制各种颜色的图像,假如有三种形状,三种颜色,这样就有九种类,但是如果使用桥接模式,只需要一个桥梁,便可以把两个连接起来,就可以*的选择可绘制的形状和颜色了。
https://www.cnblogs.com/chenssy/p/3317866.html
桥接模式与策略模式
桥接模式强调双方的run time delegation linking,而策略模式强调一方run-time使用另一方的“算法”,并且“算法”通常实现为“类的某个方法”的形式,strategy的目的并非在“调用算法的类”与“被调用算法所在的类”之间建立起永久联系,而只是帮助前者临时使用后者中的“算法”,前者无需永久保存后者的实例。
举个比较形象的例子,策略模式相当于:使用螺丝刀的时候,针对不同的工作任务,选取不同的“刀头”,但目的并非将螺丝刀与刀头组合起来建立永久的delegation,而只是临时通过delegation完成任务(即调用刀头的“算法”),然后二者再无联系。也就说强调动态调用。
而桥接模式强调结构关系的动态绑定。

(2)代理模式

某个对象比较“敏感”/“私密”/“贵重”,不希望被client直接访问到,故设置proxy,在二者之间建立防火墙。
代理的三种类型
远程代理:为一个对象在不同的地址空间提供局部代表 (缓存机制) 。
虚代理:根据需要创建开销很大的对象。
保护代理:提供访问保护。
【软件构造】课件精译(十四)面向可维护性的设计模式
举例
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
这个例子可以看到,通过ProxyImage封装了一层,两者同时继承了Image,但是其内部在display是会判断,从而延迟装载,仅在需要时装载。
代理模式与适配器模式
适配器模式是为了消除不兼容,目的是B以客户端期望的统一的方式与A建立起联系。 而代理模式则是为了隔离对复杂对象的访问,降低难度/代价,定位在“访问/使用行为” 。

(3)组合模式

容器中具有基本元素和组合元素两类,如何以统一的方式访问所有的元素 ?
【软件构造】课件精译(十四)面向可维护性的设计模式
举例
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
这种设计模式下,在对象间建立了从属关系,上层在输出自己的结果后还会调用子类的方法输出结果。
组合模式与装饰器模式
组合模式目的是在同类型的对象之间建立起树型层次结构,一个上层对象可包含多个下层对象 ,而装饰器模式强调的是同类型对象之间的“特性增加”问题,它们之间是平等的,区别在于 “拥有特性”的多少,每次decoration只能作用于一个object。

三、行为型模型

(1)观察者模式

如何一个对象的状态受另一个对象影响?或者举个例子“粉丝”对“偶像”感兴趣,希望随时得知偶像的一举一动。那么,可以让粉丝到偶像那里注册,偶像一旦有新闻发生,就推送给已注册的粉丝(回调callback粉丝的特定功能)。即一种“发布-订阅”形式,发布方的变化,会通知订阅方,而订阅方在发布方注册。
需要定义一下四种对象:
Abstract subject: 维护订阅者列表
Abstract observer:定义更新的协议
Concrete subject:维护数据,修改后通知订阅者
Concrete observers:得到通知后,修改自身的状态
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
举例
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
这个例子中,DigitObserver和GraphObserver是两个观察者,抽象类中有个List用来存储观察者,并且提供相应的通知方法。RandomNumberGenerator 继承了抽象类NumberGenerator 后,执行过程中会不断调用通知,即遍历这些观察者并调用其更新的回调方法。不过,也不一定要在回调时传this,可以事先将“偶像”的地址记录在观察者中。
这种模式下, 主体(被观察者)和观察者之间松耦合,主体不需要关注观察者 ,不过主体需要存储观察者列表。
Java中的Observer和Observable
【软件构造】课件精译(十四)面向可维护性的设计模式

(2)访问者模式

对特定类型的object的特定操作(visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类。
本质上:将数据和作用于数据上的某种/些特定操作分离开来。
这样,便可以为ADT预留一个将来可扩展功能的“接入点”,外部实现的功能代码可以在不改变ADT本身的情况下通过delegation接入ADT。
当操作逻辑发生变化时,只需要修改visitor的实现,而不需要修改类。
【软件构造】课件精译(十四)面向可维护性的设计模式
举例
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
例如上面这个例子,计算价格的方法写在继承了Visitor接口的实例ShoppingCartVisitorImpl内部,而当遍历商品时,会调用商品的accept方法,这个方法会回调visitor的visit方法。这也便实现了将处理数据的功能 delegate到外部传入的visitor。这样,我们并没有为商品增加计算价格的功能,但是通过visitor便扩展了它的功能,这要编写实现相应接口的类即可,只要更换visitor的具体实现,即可切换算法。
访问者模式与迭代器模式
迭代器:以遍历的方式访问集合数据而无需暴露其内部表示,将“遍历”这项功能delegate到外部的iterator对象,而访问者模式在特定ADT上执行某种特定操作,但该操作不在ADT内部实现,而是delegate到独立的visitor对象,客户端可灵活扩展/改变visitor的操作算法,而不影响ADT。
策略模式与访问者模式
二者都是通过delegation建立两个对象的动态联系,但是Visitor强调是的外部定义某种对ADT的操作,该操作于ADT自身关系不大(只是访问ADT),故ADT内部只需要开放accept(visitor)即可,client通过它设定visitor操作并在外部调用。而Strategy则强调是对ADT内部某些要实现的功能的相应算法的灵活替换。这些算法是ADT功能的重要组成部分,只不过是delegate到外部strategy类而已。
区别在于visitor是站在外部client的角度,灵活增加对ADT的各种不同操作(哪怕ADT没实现该操作),strategy则是站在内部ADT的角度,灵活变化对其内部功能的不同配置。

(3)中介者模式

多个对象之间要进行交互,不是直接交互,而是通过mediator,实现消息的广播,从而将对象之间松散耦合,利于变化。
【软件构造】课件精译(十四)面向可维护性的设计模式
如果不通过这样的设计模式,对象会互相其他对象,导致很高的耦合。
举例
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
这种模式的结构比较简单,对象(继承了Colleague接口)间的通信通过中介(继承了Mediator接口)来实现,可以通过中介发送和接收消息,而中介者有发送和添加Colleague的方法,通过一个ArrayList保存内容。
观察者模式与中介者模式
观察者模式是一组object对另一个object B的状态变化感兴趣(1对多),所以对其进行观察。B维持着一个对自己感兴趣的object list,一旦自己发生变化,就通知这些object。双方地位并不对等,一方只是“通知”,另一方接到通知后执行特定行为。 即自己来广播,其他对象接收。
而中介者模式是一组同类型的object,彼此之间相互发/收消息(多对多),不存在谁观察谁的问题,所有object都对等。每个object都维持一个mediator,将通讯的任务delegate给它,自己只负责send和receive即可,无需了解其他object,实现了“解耦”。 即第三方中介负责广播(类似于“邮件列表”)。

(4)命令模式

有的时候客户端希望执行指令,但不想知道指令的细节,也不想知道指令的具体作用对象。
而命令模式的目的则是将“指令”封装为对象,指令的所有细节对client隐藏,在指令内对具体的ADT发出动作(调用ADT的细节操作) 。
例如工具箱的设计者无法知道请求的接受者或执行的操作 ,只有使用工具箱的应用知道该由哪个对象做哪个操作。
命令模式通过将请求本身变成一个对象来使工具箱对象可向未指定的应用对象提出请求。这个对象可被存储并像其他的对象一样被传递。
实现了 将所有对client提供的指令与内部执行的ADT操作彻底分开,指令对外看来是“黑盒”——进一步“变本加厉”的封装!
【软件构造】课件精译(十四)面向可维护性的设计模式
Client 通过Invoker执行Command,Command通过调用Receiver执行动作。优点是客户端不需要知道所存储命令对象和操作的细节。
举例
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
从例子中可以看到,Client的指令通过Invoker完成,向其添加或执行指令,客户端并不清楚指令的执行细节。而Invoker维护了一个栈,用来存储指令,用户的指令继承了Command接口,而具体实现交给Performer。
外观模式与命令模式
两者均强调对某个复杂系统内部提供的功能的“封装”,对外提供简单的调用接口,简化client的使用,“隐藏”细节 。
不过命令模式强调将指令封装为了“对象”,提供了统一的对外接口(execute) 。 外观模式没有显式的“对象”,仍然通过类的方法加以调用。

(5)职责链模式

有时,针对一个请求,可能有多个处理模块,并且各种不确定情况存在,不能以“硬编码”的方式指明按何种次序调用处理模块。
职责链模式的目的则是避免在请求方和各handler之间的紧耦合:构造流水线,请求在其上传递,直到被处理为止。
客户端只需在流水线的入口发出请求即可,请求自动在流水线上传递,直到被处理。
处理器对象的数目和类型事先都不确定,需要动态配置,可以使用递归组合的方式,将多个 handlers连成“职责链” 。
另外,请求的发起者无需维护所有可能的handler对象,只需记录职责链的入口;每个handler只需要维护“下一个”handler即可,从而简化了各handlers之间的连接关系。
【软件构造】课件精译(十四)面向可维护性的设计模式
举例
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
可以看到,客户端的直接作用对象是抽象类LoanApprover。而LoanApprover中记录了下一个LoanApprover。Director、Manager、VicePresident三个类实现了抽象类LoanApprover。这样便实现了一个由实现了LoanApprover接口的对象组成的“职责链”。
再看下面一个例子,逻辑相对复杂了一点:
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
【软件构造】课件精译(十四)面向可维护性的设计模式
这个例子中出现了职责的传递,如果满足不了处理条件则交给下一个节点。
访问者模式与职责链模式
这两个设计模式都是将“数据”与“作用于数据上的客户端定制操作”分离开来,这样操作可以灵活增加、运行时使用的操作可以动态配置、多个操作的执行次序可以动态变化。
区别主要有两个:
区别1:visitor只定义了一个操作,chain of responsibility定义了一组操作及其之间的次序
区别2:visitor中,client创建visitor之后传入ADT,ADT再将执行权delegate到visitor;chain of responsibility中,控制权完全在各个handler之间流转,ADT(request对象)完全感受不到。

三、可重用性和可维护性设计模式的高级考虑

使用设计模式的线索(1)
“独立于制造商”,“设备独立”,“必须支持一系列产品”=>抽象工厂模式
“必须与现有对象接口”=>适配器模式
“必须与多个系统连接, 其中一些将在未来开发“,”早期原型必须展示“=>桥接模式模式
”必须与现有的一组对象接口“=>外观模式
使用设计模式的线索(2)
“复杂结构”,“必须具有可变深度和宽度”=>复合模式
“必须是位置透明”=>代理模式
“必须是可扩展的”,“必须是可扩展的”=>观察者 模式
“必须提供独立于机制的策略”=>策略模式

补充:23种设计模式

创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。
结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。
行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
https://www.cnblogs.com/malihe/p/6891920.html