设计模式-结构型

DinS          Written on 2017/11/11

继续介绍设计模式,本文介绍结构型。

结构型影响对象之间的连接方式,确保系统变化不需要改变对象间的连接。
这句话可能需要解释一下,结构型的用武之地是在一个已经系统化的OOP程序中做出改变。
因为已经系统化,所以想要进行改动需要额外注意。这里的改动跟应对变化不一样。OOP的强项就是应对变化,那么这里怎么又需要额外注意了呢?
这是因为OOP取决于对象之间的连接,即接口。在接口不变的情况下各个部分可以独立变化。但是如果想要改变接口呢?这个相当于对系统做大手术,会出现很多问题。
所谓的结构型,就是应对这种情况进行的处理。通过对已有对象的某种包装,达到不改变接口同时部分地改变对象的效果。

一、代理模式(proxy pattern)

代理模式应对这样一个问题:想对某个类增加一些额外的小功能,比如计数或者输出日志,但是同时不影响这个类相关的已有代码。
代理模式的用法是从基类派生出一个代理类,与其他正常的派生类处于同一个层级上,同时在其内部有指针指向某个派生类,实现派生类的所有接口,这样就成为了派生类的代理。这样一来如果要增加小功能,就在代理类中增加即可。

比如针对水果商人,我们有一个新需求:统计苹果销售次数。
苹果作为水果的派生,已经在系统内部。如果仅仅为了一个苹果类去改变基类,那么其他的所有派生类都需要做更改,不划算。
如果直接在苹果类内部更改,就造成了与其他派生类的不一致,容易留下隐患。
于是使用代理模式最好,UML如下:

注意到苹果代理类AppleProxy与其他派生类是同样级别的。
代码实现如下(还是用简单工厂那套代码容易理解):

代理类的实现就只有两条要求:
成员变量为指向代理对象的(智能)指针;实现代理对象的全部接口。

再来看看实现:

实现就更简单了,构造时准备好代理对象,然后接口全全部调用代理对象对应的接口即可。在这个框框下视需求增加一些小功能即可。

最后得益于简单工厂模式,我们可以很容易地把代理类严丝合缝地嵌入原来的代码中而不需要做额外工作:

大图点这里

这样所有原来是Apple类的都变成AppleProxy了。再次显示了工厂模式的强大。

运行结果:

输出了新增加的功能,同时计算结果正确,我们也没有动原来的代码。
这就是代理模式的效果。

总结一下代理模式:接口一致,增加额外小功能

这里一直在强调小功能,是的,代理模式非常适合小功能的添加。
如果涉及到大改,那么还是老老实实地对类体系进行改动为好。

二、适配器模式(adapter pattern)

在涉及到第三方库时,通常会用到适配器模式。
这是因为第三方提供的内容通常是没有跟我们协商过的,所以接口必然会不太一样。将第三方接入我们的OOP系统,同时避免对现有系统做出大改,这是一个常见的情况。适配器将发挥用武之地。

在水果商人中,我们的折扣券系统是由第三方提供的,接口统一是返回double类型作为折扣力度。
假设现在新引入一家折扣券供应商,其提供了一个类GD来计算折扣力度,函数名称是GenerateDiscount,返回的是string,字符串里包括了折扣力度和其他信息。显然不能直接接入系统,因为我们的Coupon类使用的是GetDiscount,并且返回double,所以可以制作这样一个adapter类。

偷个懒,写个示意代码就好了。
适配器模式不难理解。注意这里把第三方提供的类作为适配器的私有成员了,这样是一个较好的选择。

总结一下适配器模式:功能一致,接口不同
之所以叫适配器就是用来转换接口用的。

至于其他设计模式可见《OOP实例-水果商人(二)》文末。