设计模式(二):结构型模式
适配器模式
软件设计的时候,有时我们现有的类就能够满足某一个功能需求了,但是现有的类提供的接口却并不是客户所期望的,如现有类的方法名和客户要求的不一致等。这时可以采用适配器(Adapter)模式对原有的类进行包装(Wrap),从而适配客户的需求。
这个模式比较好理解,就是通过包装来修改已有类的接口,就好像电源适配器一样。需要注意的是适配器(Adapter)类是持有被适配类(Adaptee)的实例的,不存在继承关系。
下面用一个例子来说明,假如我们有一个可以输出两种修饰符号的类:
1 | class Banner { |
但是客户的需求是实现强语气和弱语气两种输出方式:
1 | // define output interface |
我们刚好通过一个适配器来将 Banner 类的方法进行修正:
1 | // As adapter of banner, make banner adapt to "Print interface" |
使用如下:
1 | int main() { |
桥接模式
桥接模式(Bridge Pattern)将抽象部分和实现部分分离,使它们可以独立变化。
桥接模式定义比较难理解,这里举一个简单的例子说明为什么要有桥接模式这种设计。如果我们需要表示不同颜色和不同形状的物体,如红色、绿色以及圆形和三角形,倘若我们只使用形状类 Shape 作为基类进行设计,我们需要定义 4 个子类来分别表示红色圆形、绿色圆形、红色三角形和绿色三角形。倘若有更多的颜色和形状,我们的子类数量将会乘性的增加。
桥接模式通过将颜色和形状变化的维度进行桥接,可以解决这个问题。具体的实现思路是,将颜色定义成一个接口,形状类持有这个接口。那么颜色这个维度可以和形状解耦单独变化。桥接模式又称为柄体(Handle and Body)模式。
上面的例子实现如下,首先是颜色接口的定义:
1 | class Color { |
然后定义形状抽象类,并持有一个颜色的接口:
1 | class Shape { |
这样的话只需要在 Shape 类中调用 Color 接口的方法,不需要管其实际的子方法。
使用如下:
1 | int main() { |
装饰模式
装饰模式(Decorator Pattern)通过对原有类进行包装来扩展原有类的行为,而不会改变原有类的结构,实际上就是原有类的一个包装(Wapper)。
实际上扩展一个类的功能可以采用继承或组合(持有它的实例)两种方法,一般而言需要避免继承方法,而采用组合方法,这里的装饰模式就是采用组合方法来扩展原有类的功能。当然这里一般也会将装饰器定义成原类的子类。
举例如下,原有的类是颜色类:
1 | class Shape { |
为了对其进行功能扩展,实现一个它的装饰类:
1 | class ShapeDecorator : public Shape{ |
使用时,调用装饰类的方法:
1 | int main() { |
外观模式
一个系统可能会存在若干个子系统,为了对外简洁和使用简单,可以使用一个外观(Facade)对象作为系统的门面,这样可以将各个子系统隐藏起来。外观模式(Facade Pattern)比较容易理解,就是为了客户使用方便而定义的一个接口类。
若系统有若干形状的子系统:
1 | class Shape { |
1 | int main() { |
享元模式
享元模式(Flyweight Pattern)通过共享技术实现系统中相同对象的重用,一般是小对象,从而减少创建对象的数量,以减少内存占用和提高性能。
享元模式一般提供一个工厂类,这个工厂保存一个 Map, 工厂接受 Map 的键返回对象,当多次请求同一个键时可以返回相同的对象,从而实现对象的共享。一个比较常见的例子就是 Java 的字符串对象,当创建字符串时,若字符串缓存池中有该字符串则直接将其返回,否则创建该字符串并将其加入缓存池中。
下面通过形状和形状工厂的例子来说明问题,首先是形状类和 Circle 子类:
1 | class Shape{ |
然后定义一个工厂类,为了重用 Circle 对象,工厂类维护一个哈希表,保存已经创建的对象,键值为字符串(形状的颜色),当请求的对象存在时直接返回,不存在时创建新对象并将其加入哈希表中。
1 | class ShapeFactory { |
使用时根据颜色来获取 Circle 对象,这里假如有 5 种颜色的对象,并随机的取 20 个对象出来使用:
1 | int main() { |
运行结果如下:
1 | create circle of color:Red |
可以看到只创建了 5 次对象,大量的对象被复用了,这就是享元模式的作用。
代理模式
代理模式(Proxy Pattern)在之前的博客中已经介绍过了:Spring框架(三):动态代理和AOP,AOP 通过动态代理的方式实现了对原有功能的扩充,并且不会影响到原有类的结构。代理模式通过称为代理的第三方来间接提供服务。从而原有的服务进行扩充或隐藏。
这里还是把之前的例子拿来用,但是是静态代理的实现,首先还是有一个能够添加和删除商品的服务,并有一个实现了该接口的子类:
1 | class GoodService { |
为了对 OrangeService 进行功能扩充,即在每次方法调用之前进行记录。可以使用一个代理类来对原有类方法进行增强:
1 | // proxy of OrangeService |
代理类也继承自 GoodService,并持有一个 OrangeService 的实例。
使用的时候通过代理类而不是原有类:
1 | int main() { |
参考文献
- https://design-patterns.readthedocs.io/zh_CN/latest/structural_patterns/structural.html
- https://www.runoob.com/design-pattern/design-pattern-tutorial.html
此外,本文的代码可以在https://github.com/xiaoqieF/Design-Pattern-notes中找到。