软件构造课堂笔记(2)初识设计模式
文章目录
引入
设计模式是程序设计人员在以往编程经历的基础上,总结出的一套具有广泛适用性和实用性的设计策略。遵循设计模式的原则,有利于开发出一套易于理解、易于复用,同时结构也足够精巧的程序。
接下来我将简要说明课上介绍的六种设计模式,按照设计层面可以分为结构性和行为型。理解如果有冲突,还望指出。
结构型模式
这一类模式主要是想解决程序结构设计方面的问题。
Adapter 适配器模式
适配器模式主要是为了解决客户端要求和类/接口使用方式的冲突。
比如说,有一个类完全能够满足客户的需要,只是其对外暴露的API和客户能提供的信息类型或数量不同,导致客户无法直接委派这个类,无法完成相应的工作。
就好像你知道一个外国的程序员能完成中国的产品经理所分配的任务,但是二者语言不通,产品经理没有办法和程序员说明具体需求。那么解决方式也很简单,就是让一个会外语又会中文的人来充当翻译中介;对于产品经理来说,他只要向中介说明需求即可;而对于程序员来说,他只要知道中介对他下达了什么指示即可。二者的问题也就迎刃而解了。
UML图表示如下:
客户Client需要一个符合ITarget接口标准的实现类,原有类Adaptee完全可以完成任务,但是接口不符合ITarget接口标准。于是我们创建了一个Adapter类作为ITarget接口的实现类,Adapter按照ITarget接口标准接受Client的委托,再委托给Adaptee完成相应任务。这样就实现了原有类的复用。由于这种设计方式就像现实世界中给充电器尾巴上装上适配器,故称之为适配器模式。
其实也仅仅只是一种转换输入信息格式的封装。这种封装仅通过委派实现,没有改变Adaptee类的内部结构,也不需要了解其内部结构。很好地体现了隔离信息的设计特点。
Decorator 装饰器模式
装饰器原则可以说是继承的一种替代方案,主要用在这样一个场景:
我们可能有从一个基类当中继承出很多子类的需要,而这些子类又有着十分相似的多个功能特征;同时,客户端可以根据我们提供的这些特征完成高度的定制。注意,这里的功能特征是一种“附加”功能,即在原有API基础上,多完成一些任务。如果要通过继承修改的话,也只是在父类方法体中多加一些东西。
这样的话,如果仅仅使用继承来完成任务,继承树将会变得非常复杂,中间可能有很多过渡性的子类。这么设计就会造成设计复杂性提高,产生大量的代码重复,出现“组合爆炸”的效果。
那么我们为什么不将这些特征单独分出来呢?装饰器模式就是这么干的。
装饰器模式恐怕是这六种设计模式中最难理解的了。我将分解设计步骤,说明这么每个步骤的目的和理由。
UML图如下:
- 首先,对于每个不同特征的子类型,我可以设计一个统一的接口Component,可以调用特定方法(注意前文所述,API并没有改变,这些功能都是附加上去的,不影响其他特征的实现)。
- 接着,这个接口当然得有一个最基本的实现类ConcreteComponent,这个实现类没有附加任何的功能。如前文所述,用继承的方法实现的话,这个类就是一个基类,其他所有的类都应该以它为模板。
- 那么我们该如何在基类的基础上进行个性化定制呢?不利用继承的话,最简单的想法就是通过委派关系对基类实现一个封装。Decorator就是这么做的,它实现了Component接口,同时通过委派任务给一个Component接口,就可以实现对满足Component接口的所有实现类(包括ConcreteComponent类)的使用了。以后客户在使用Component接口类时,具体实现可能就不用Concrete Component,而是附加了一下功能的Decorator了。但是这样也只能完成一种附加功能的扩展呀,甚至没有完成多功能的定制。
- 为了满足多功能定制的需求,我们需要创造更多像Decorator一样的类,分别对特定附加功能进行实现。为此我们将对Decorator进行继承。而Decorator作为模板也将转化为抽象类,本身不能实例化,但是它的子类可以。这样,每个子类都对它所包含Component类型进行了拓展,可以说就是一种“装饰”。至此,装饰器模式的继承树设计基本就要完成了。
- 说到这里,那到底该怎么用呢?我们再在每个作为“装饰器”的子类创建一个能够根据传入类型设置委托的Component接口具体实现类的构建方法。这样,我们就能通过这样一种十分通俗的方式定制一个即有ConcreteDecoratorA类的附加功能又有ConcreteDecoratorB类附加功能的Component类:
Component comp = new ConcreteDecoratorA(new ConcreteDecoratorB(
new ConcreteComponent));
这就好像在一个基础对象的前面加了好多修饰词一样,每个修饰词都代表着附加的功能。这样,利用装饰器模式,不但可以完成多个特定功能的定制,也能更直观地理解这种创建方法。
Facade 外观模式
外观模式基于一种很朴实的想法:尽可能降低用户使用我的API的成本,让API设计得尽可能简单明了。
某种程度上说,外观模式可以理解为一种只有假象客户、有多个委托对象的适配器模式。
如果用户真的直接去操作整个程序分散在多个类当中的API,学习和使用的成本就非常高,而且很容易出错;如果对整个程序进行封装,并将对外接口进行重新设计,将使用细节都由API设计者完成,那么用户就会上手得更快,也更不容易出错。在复杂度的角度上说,就能减少外部程序对内部的耦合。
太好理解了,连UML图都不用了。
行为型模式
行为型模式针对程序的行为设置提出指导性意见。
Strategy 策略模式
策略模式适合于有多种不同的算法来实现同一个任务的情况。但是我们不需要自己进行选择,而是需要client动态决定是否切换算法。
为了实现这种模式,我们需要设计一个算法接口,继承出我们需要的各种算法类。同时,我们要想办法在使用算法接口的类中避免切换算法产生的if-else选择分支语句,这类语句在扩展出新的算法类时,不好修改,影响复用性,也没有体现出策略模式的精髓。
策略模式设计需要完成的一个使命就是,在没有选择分支语句的情况下,不同算法都能利用算法类在既定框架下执行。
UML图如下:
Templete 模板模式
模板模式简单地说就是继承关系的设计理念:共性的东西放在基类/抽象类,个性的东西放在子类,利用继承和重写完成特定模板下程序的定制。
所谓的“白盒框架”就是模板模式思想的一个体现。所以不妨就把这种模式当成对一个框架进行复用。
这个框架首先发源于一个基类,这个基类不能被继承,所以要用抽象类表示,抽象类内部可以有具体方法的实现,要用final固定,防止后代修改;也会有需要后代实现的方法,用abstract修饰符强制后代完成任务。这样,继承关系得到的后代,就是在基类模板基础上完成的个性化定制了。
UML图如下:
Iterator 迭代器模式
迭代器模式常用于:客户端希望遍历被放入容器/集合类的一组ADT对象,而不关心容器的具体类型。
也就是说,我们要能够为一个集合类创建一个能够遍历内部子对象的方式。而这种方式Java已经给我们现成的接口Iterable了,我们让集合类实现Iterable,强制它完成Iterator()方法的设计,为了返回一个满足Iterator接口的对象,我们还需要完成一个内部类的设计,这个内部类需要继承Iterator接口,再实现Iterator的各个方法。
如此,客户就能够通过Iterator()完成对此集合类的遍历了。
UML图如下: