第六章代码的可维护性——可维护性的设计模式

可维护的设计模式主要分为三种,分别为:创造性模式(前三个)、结构化模式(中间三个)和行为化模式(后五个)。接下来我们将围绕这三种模式分别进行详细讲解:

1.创造性模式(Creational pattern):

1. 工厂方法模式(Factory Method pattern)

也叫虚拟构造器(Virtual Constructor)。当客户端不知道要创建哪个具体类的实例,或者不想在客户端代码中指明要具体创建的实例时,可以采用工厂方法。

其实现方法是:定义一个用于创建对象的接口,让其子类决定实例化哪一个类,从而使一个类的实例化延迟到其子类。

类图说明:



啊!当然还是要有大栗子的……






其实就是绕了一个弯嘛……不过可不要小瞧这个弯。就是因为这个弯消除了将特定于应用程序的类绑定到代码的需要,与此同时代码中仅需处理产品接口,因此他可以与任何用户的产品一起使用。

还记得上一节中的OCP原则么?这里就体现了OCP的原则,对拓展开放,对修改已有代码的封闭。


2. 抽象工厂模式(abstract Factory)

试想我们现在有一个仓库类,要控制多个设备,但是这些设备的制造商不同,控制接口有差异。那么显然对于这种情况工厂模式是不适用的,所以这就需要抽象工厂模式。

其提供了一个接口用来创建一组相关的/相互依赖的对象,但并不需要指明具体类。

还是焗栗子吧:

类图:


应用:



抽象工厂创建的并不是一个完整的产品,而是一个产品族。什么是产品族呢?举个栗子,假设我们要造汽车,我们现在有轮胎,发动机等等一系列造汽车的产品,那么我们需要将他们组装起来构成一个产品族这就是我们造好的汽车。注意在抽象工厂中,多个不同种类的产品均有不同种类的类型,比如说发动机分为很多种类,根据用户需要来采用不同的发动机。也就是说各个产品的创建过程对客户端可见,但搭配不能改变。本质上 抽象工厂就是把多类产品的工厂方法组合在了一起。

另外唠叨一句,这里注意并不是完全的包装,对于客户端抽象工厂创建的每一个对象客户端均知晓!为什么重点强调这一点?因为下一个模式我们将会提到builder模式,在该模式下才是完全的封装好的,所有构建细节都会对客户端隐藏。具体可以往下看。


这是不用抽象工厂的实现,乍一看没什么问题,但是假如产品AB分别有两种类型我们姑且叫他A1/A2和B1/B2,如果规定A1和B2不能组合在一起,那么这样显然就会出现问题。也就是说抽象工厂固定了各个产品的搭配,不能随意组合。

抽象工厂与普通工厂对比:

  1. 抽象工厂会创建多个类型对象,而普通工厂只会创建一个
  2. 抽象工厂包含有多个工厂方法,而普通工厂只会包含一个
  3. 抽象工厂使用的是组合/委派实现,而普通工厂使用的是继承/子类型实现


3.构造器模式(Builder)

构建器模式是用来创建一个包含多个部分的复杂对象的。

注意此处客户端要的不是一堆零散的对象(抽象工厂那样的结果),客户端需要的是一个完整的产品,并不关心细节的组成部分。用抽象工厂中的栗子,抽象工厂模式可以理解为汽车组装公司,他需要知道汽车的各个组成部分并装起来。而构造器模式可以理解为购买汽车的消费者,其不在乎具体的内部构造如何构建,她仅仅是想开着车出去兜兜风而已。

类图解释:



来个大栗子吧!




抽象工厂模式与构建器模式对比:

  • Abstract Factory创建的不是一个完整产品,而是“产品族”(遵循固定搭配规则的多类产品实例),得到的结果是:多个不同产品的实例object,各产品创建过程对client可见,但“搭配”不能改变。
  • Builder创建的是一个完整的产品,有多个部分组成,client不需了解每个部分是怎么创建、各个部分怎么组合,最终得到一个产品的完整 object 。

模板模式(Template)与构建器模式对比:

  • 模板模式是为了复用算法中的公共结构(次序)
    定义了一个操作中算法的骨架(steps),而将具体步骤的实现延迟到子类中,从而复用算法的结构并可重新定义算法的某些特定步骤的实现逻辑。其强调步骤的次序,子类override算法步骤。
  • 构建器模式目的是创建一个复杂的对象,灵活拓展
    将一个复杂对象的构造方法与对象内部的具体表示分离出来,同样的构造方法可以建立不同的表现。不强调复杂对象内部各部分的“次序”。子类override复杂对象内部各部分的“创建”
    其通过派生出新的builder来构造新的对象体现了OCP原则


2.结构化模式(Structural Patterns)

1.桥接模式(bridge)

桥接模式是OOP最基本的结构模式,通过委派+继承来建立两个具体类之间的关系(DIP依赖转置原则,具体依赖于抽象)

(其实个人理解就是不能更换策略的策略模式)

类图解释:


废话不多说!大荔枝!




(这就是强化的策略模式嘛……)

然而还是说一下对比吧,其实也就是强调方面不同而已:

  • 桥接模式中,强调双方的run-time委派连接
    一个类A的对象中有其他类B的对象作为其组成部分,但A的对象具体绑定到那个具体子类来实现?在运行时通过委派加以组合并永久保存这种关系
  • 策略模式中,强到一方的run-time使用另一方的算法
    “算法”通常实现为“类的某个方法”的形式,strategy的目的并非在“调用算法的类”与“被调用算法的类”之间建立永久联系,只是帮助前者临时使用后者中的算法而已,前者无需永久保存后者实例


2. 代理模式(Proxy)

代理,为什么要代理呢?当然是被代理的对象可能比较敏感,私密或者贵重等等,不希望被客户端直接访问到,所以才设置代理,在二者之间建立防火墙(oh!wall!holy shit!)

类图解释:



这个例子比较简单:





来解释一下,这并不是说display方法会造成严重的代价,其实说明从磁盘装填文件需要很严重的代价,而display方法需要磁盘中的数据而已,但大部分的程序是不需要这些是代价变的很高的数据的,所以我们就可是使用代理类,如果需要磁盘中的文件时在执行文件装载。这样就不需要用到一张图片就装载一张图片,即使这个图片根本就没有用到display方法。


与适配器模式的对比(Adaptor):

  • 适配器模式目的在于消除不兼容,其根本目的在于希望B按照客户端的期望的统一方式与A建立起联系。
  • 代理模式目的在于隔离对复杂对象的访问,降低难度/代价,定位在“访问/使用行为”


3. 组合模式(Composite)

组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,组合模式又可以称为“整体—部分”(Part-Whole)模式,它是一种对象结构型模式。

类图:




emm……这个模式,还是看栗子吧……



如何?有什么感想?这……确定不是数据结构的多叉树?没错啊!这个模式就是强调了一个树状的层次结构。

和装饰器模式(Decorator)对比:

  • 组合模式目的在于在同类对象之间建立起一个树形层次结构,一个上层对象可以包含多个下层对象
  • 装饰器模式目的在于强调同类型之间的“特性增加”问题,他们之间是平等的,区别在于拥有特性的多少,每次装饰只能作用于一个对象。



3.行为化模式(Behavioral Patterns)

1.观察者模式(Observer)

假设,我们有一个爱抖露idol,她有很多很多的fans,那么这些小粉丝十分喜欢他们的偶像,想随时得知偶像的一举一动,那么怎么办呢?偶像有微博呀,他们就可以去申请偶像的好友位,偶像一旦有故事发生,就会推送给好友位的粉丝(callback)

类图解释:



其实也是一个定义看起来很难得模式,实际的代码看一个栗子也就懂个基本了:

如果以开头的偶像粉丝为例,这个是偶像:


粉丝s:

客户端:



值的一提的是,java中这种模式结构已经帮我们定义好了。需要用的时候直接用就可以啦(不得不说java确实比c++什么的贴心不少)……




2.访问者模式(Visitor)

对特定类型的对象的特定操作(visit),在运行时将二者动态的绑定在一起,该操作灵活更改,无需更改被visit的类。

本质上是将数据与作用在数据上的某种特定的操作分离开

类图解释:


emm……和策略模式很像,非常像,及其相像!来是来看个代码吧:





这……不就是策略模式嘛?没错,从实现的角度来看的的确确就是策略模式,委派其他类实现不同的算法。不过二者又有着根本的不同:

  • 观察者模式强调的是外部定义某种对ADT的操作,该操作与ADT自身的关系不大(只是访问ADT),故ADT内部中需要开放accept(visitor)即可,客户端通过它设定visitor操作并在外部使用。
  • 策略模式强调是对ADT内部某些实现的功能的相应算法进行灵活的替换。这些算法是ADT功能的重要组成部分,只不过是委派到外部策略类而已。

也就是说观察者模式是站在外部客户端的角度,灵活的增加对ADT的各种不同的操作(哪怕ADT没实现该操作),策略模式则是站在内部ADT的角度,灵活变化对其内部功能的不同配置。可以这么理解,策略模式是代码复用阶段的,是构建ADT时使用的,是应满足表示独立性的,如何实现决不能被用户知道。但是观察者模式不同,他就是让用户选择不同方法观察的。他是站在维护的角度上的,这么说可能容易懂些?

观察者模式也与迭代器模式有一些相像,现在来说说这两者区别:

  • 迭代器模式是以遍历的方式访问集合数据而无需暴露其内部表示,将“遍历”这项功能委派到外部iteractor对象。
  • 观察者模式是在特定ADT上执行某种特定操作,但操作不在ADT内部实现,而是委派到独立的visitor对象,客户顿可以灵活地拓展/改变visitor的操作算法,而不影响ADT。

总而言之就是观察者模式是站在用户层面上,可以拓展改变算法的一种可维护性的设计模式,有点站在迭代模式和策略模式中间的位置的意味。而迭代器模式和策略模式是站在ADT角度的,是可复用性的设计模式。


3.中介/传递者模式(Mediator)

多个对象之间要进行交互,不是直接交互,而是通过mediator来实现消息广播,从而将对象之间松散耦合,利于变化。

类图解释:



还是来看个代码吧!(这节设计模式怎么这么多呀==!)



这是客户端的使用:


在行为上可能与前面说的观察者模式类似,所以来谈一下对比吧:

  • 观察者模式是一个对象A对应一组对象的状态(1对多),A维持这一个对自己感兴趣的对象列表,一旦自己发生变化就通知这些对象。双方地位是不对等的,一个通知,另外一群负责接受。(自己广播,其他对象接受)
  • 传递者模式是一组同类型的对象,彼此间接受发出消息(多对多),所有对象都是平等的,每个对象都维持这一个传递者,将通讯的人物委派给他,自己只负责接受与发送就好,无需了解其他对象,实现了解耦。(第三方中介负责广播)


4.指令模式(Command)

其想解决的问题是客户端需要执行指令,但不想知道指令的细节,也不想知道指令的具体作用对象。

核心思想:将“指令”封装为对象,指令的所有细节对客户端隐藏,在指令内对具体ADT发出动作(调用ADT的细节操作)

还是栗子?梨子?李子?例子!




这个倒是个新东西,然而表示没怎么用过呀==!

外观模式和他很像,均强调了对某个复杂系统内部提供的功能的“封装”,对外提供简单的调用接口,简化客户端的使用,隐藏细节,那么说一下他与外观模式的区别吧:

  • 指令模式强调了将指令封装为对象,提供了统一的对外接口。
  • 外观模式没有显示的对象,仍然通过类的方法加以调用


5.职责链模式(Chain of responsibility)

终于到最后一个了T_T……

当一个请求可能有多种处理模块,各种不确定的情况存在是,不能以“硬编码”的方式指明按何种次序调用处理模块。

所以希望这种模式来避免在请求方与各个处理器之间的紧耦合。

核心思想是:构造流水线,请求在上方传递,知道被处理为止。

客户端只需在流水线入口处发出请求即可,请求自动在流水线上传递,直到被处理:

类图解释:



由于处理器对象的数目与类型实现都不确定,所以需要动态的配置,使用递归组合的方式将多个处理器连成“职责链”。请求发起者无需维护所有可能的处理器对象,只需记录职责链的入口:每个处理器只需维护下一个处理器即可,从而简化了各个处理器之间的连接关系:(有点类似链表)



好了!来最后一个大栗子,真的大,大家消化好(终于结束了,客厅滑跪……):






代码还是比较简单的:

相信大家也看出来了,它和访问者模式都是将“数据”与“作用于数据上的客户端定制操作”分离开来。

原因:操作可以灵活增加、运行时使用的操作可以动态配置、多个操作执行次序可以动态变化。

但也是存在一定的区别的:

  • 访问者模式中只定义了一个操作,而职责链中定义了一组操作并且规定了他们之间的次序。
  • 访问者模式中,客户端创建了访问者会传入ADT,ADT再将执行权委派到访问者中,职责链模式中,控制权在各个处理器之间流程,ADT(request对象)完全感受不到。


4.总结:

太多了,来张图吧:





Ahhhhhhh………………终于说完了,其实这些模式也仅仅是一部分而已,设计模式还有很多,大家感兴趣可以去了解一下。对自己的代码水平提高很有帮助,然而最重要的还是自己多码不是么?当码过得代码多了,再来看设计模式会有一种:诶?这不是我做XXX的时候用到的方法吗?原来他还有个专门的设计模式呀,难不成我很nb?23333,当然是开玩笑的,不过有了以前的一点经验再看真的收获会很多!好了设计模式就说到这里,下一章我们来说一说异常。呜啦啦啦啦……

猜你喜欢

转载自blog.csdn.net/qq_37549266/article/details/80718583