1.简介

在《设计模式-可复用面向对象软件的基础》(以下简称黑书)的第二章中介绍了一系列结构型模式,例如适配器模式,桥接模式,组合模式,装饰器等等设计模式。在黑书中,结构型模式设计如何组合类和对象以获得更大的结构,采用继承机制来组合或实现。黑书第二章重点表述的是如何通过继承下的组合来完成声明和实现的分离,但是实际上,整个一章观摩下来,类继承机制反而成为了组合的阻碍,去掉类继承,这些设计模式反而变得简单易懂,这也许就是Golang摒弃class的原因。在本节中,我们总结结构型模式在Golang中是如何通过简单的组合表现的。

2.适配器/代理 (Adapter/proxy)

2.1 意图

  • 适配器:将一个类的接口转换成客户端希望的另外一个接口。
  • 代理:为客户端提供一种代理以控制对这个对象的访问。

本质上适配器和代理都是提供了一种转发的功能,在设计模式上有一定的相似性。通常适配器通过转发来实现接口的兼容,而代理在转发过程中可能涉及更多的处理,例如权限控制等。

2.2 实现

例如我们有一个充电接口plug,typeCPlug实现了TypeC类型的插头插入,usbPlug实现了USB类型的插头插入,现在只有一个usb线,为了使用usb口来充电,我们需要实现一个适配器adapter,使用adapter包含usb成员类型,并且在InsertTypeC()实现中将插入行为转发至usbPlug,此时就完成了一个适配器。

而adapter也可视为一个简单的代理模式,为usb口提供了一个使用typec的代理。

3.桥接(Bridge)/装饰器(Decorator)

3.1 意图

  • 桥接:将抽象部分与它的实现部分分离,使它们可以独立变化。
  • 装饰器:动态给一个对象添加一些额外的职责。

以上是黑书的说明,在黑书的实现上,桥接和装饰器非常类似,都是在类成员中添加一个成员变量,在初始化的时候动态赋值,以分离类的实现。然而这两个设计模式的思想和继承可以说毫无关系,仅仅是组合而已,甚至比适配器还要简单。在Golang中通过组合可以非常容易的实现以上意图。

3.2 实现

这里我们以打印机为例,我们有两台电脑,分别是win和mac,同时有两台打印机,如何通过组合,去让两台电脑兼容不同的打印机。

通过将电脑和打印机组合,我们实现了打印功能声明和实现的分离,同时通过函数SetPrinter()给电脑动态添加了打印职责。

4.组合(Composite)

4.1 意图

将对象组合成树形结构以表示"部分-整体"的层次结构,Composite使得用户对单个对象和组合的使用具有一致性。

组合(Composite)算是结构型模式中为数不多体现了组合之外思想的设计模式,其实现思想与Unix中"Every thing is a file"相类似。

4.2 实现

假设我们设计一个简易文件系统,文件夹中包含文件(整体-部分),而文件夹和文件从本质上说都是文件,都能通过Vim读取编辑。

5.外观(Facade)

5.1 意图

为子系统中的一组接口提供一个一致的调用,Facade模式定义了一个高层接口,使得这一子系统更加容易使用。

以上是黑书拗口的表达,翻译过来就是,将一系列功能封装成一个函数提供给调用方调用。

这个模式太过于基础,基础到都不应该算做一个设计模式。

6.享元(Flyweight)

6.1 意图

运用共享技术有效的支持大量细粒度的对象。

享元在黑书的结构型模式中应该算得上一个真正的设计模式,其思想主要是采用共享池去共享高频重复的对象。这个设计模式大量出现在各种文档中,通常一个十万字符对象级别的文档只需要共享三位数级别的字符对象,能够节省大量的空间以及初始化时间。

6.2 实现

例如在互联网某视频网站上,我们可以将用户分为三类,小黑子,ikun和其他,每个新用户在创建账号时可以使用add()来共享flyweight池中的身份对象,从而免去了对象的创建以及节省了空间占用。如果上亿用户都新建一个对象,即使创建一个对象的代价很小,当基数上来之后,内存空间依旧难以支撑,而flyweight模式可以完美解决这个问题。

7.总结

本节主要讲述黑书第二章,结构型模式在Golang中的使用,由于《设计模式》这本书是基于cpp,且市场上其他的设计模式大多也基于java等类似语言,所有的模式中都穿插着基类以及子类的概念,在类继承的基础上去通过组合来实现各种模式,实则隔靴搔痒,类的概念反而成为了阻碍。在Golang中没有类的概念,一切都以组合来实现,反倒让大部分设计模式变得鸡肋无用,降低了编码者的心智负担。