结构型模式
适配器
概述
本质上就是将一个对象接口和另一个对象的接口适配。实现上很简单,适配器继承+覆盖目标对象的方法,并在覆盖的方法中调用源对象的方法,达到偷梁换柱的功能
桥接
概述
将抽象与实现分离开来,使它们可以独立变化。 这是官方的解释,通俗的讲。某个类具有两个或者以上的维度变换,例如 交通工具行驶在路上,路面的材质有多种,交通工具也有多种,如果直接使用继承实现 那么 就有 MxN 种子类 太多了麻烦,桥接模式就是用于解决这种问题。
结构
桥接模式建议将抽象和实现分开。对于上述例子,路面作为抽象接口,抽象出 driverOnRoad 接口,同时包含一个 车 的指针,不同类型的车重写 driver 函数用于输出不同的车类型,在 driverOnRoad 函数中调用 车 的driver()接口,并结合 路面的材质一起输出,就完成了 车种类+路面材质的组合输出。如果要实现 另一种路面的组合,可以对路面重写 driverOnRoad 函数,更改里面的路面材质。
组合
概述
将对象组合成树形结构来表示“整体/部分”层次关系,允许用户以相同的方式处理单独对象和组合对象。
结构
这玩意就是一个 树 形状的结构,因此同组合中的元素的访问其实就是递归访问的方式。组合对象即为 节点,组件即为叶子节点,组合对象可以包含一个或多个组件,而每个组件可能类型不同,因此如果要统计一个根 组合(节点/容器)中的所有组件信息,那么里面就要区分容器/不同组件等区别,这是很难完成的。组合模式建议将 所有 组合 和 组件(节点和叶子)都抽象出几个统一的接口。组合对象拥有一个或者多个组件对象,因此组合对象的操作可以委托给组件对象去处理,而组件对象可以是另一个组合对象或者叶子对象。
总之,就是将组合 和 组件抽象为 节点 和 叶子的关系,使用同一个接口 (图中的 Component ) 去返回组合/组件中拥有的信息。靠多态的机制实现不同的类的对象调用相同的接口来获得他们的信息。
装饰
概述
为对象动态地添加功能,例如 咖啡的本体是 咖啡豆的品种,必定要选咖啡豆的种类。但是可以增加其他配料,例如牛奶,糖等,也可以啥都不加。使用这个模式之后就可以方便动态的往初始咖啡豆的种类中增加 其他配料,然后统计总的价格。
注意有个关键点,必定有个基础配置,即咖啡豆的种类;否则不选咖啡豆种类,反而选牛奶,那主体就不是咖啡了,因此此为 装饰 的本意。
结构
要实现上述功能,首先要获取每个配料 component 的价格和描述,必然要实现 get_discription 和 cost 的接口用于返回描述和花费。对于咖啡豆的种类实现这个接口即可,对于装饰类,还要继承 component 后 还要 增加一个 component 指针用于指向下一个对象。因此整个装饰器的结构就好像 一个单向链表。末端是初始的 咖啡豆种类,前一个可以是增加的某种配料,并且它的 component 指针指向咖啡豆对象,这样一次往前增加配料,最终返回的是链表头的对象。返回 cost 的时候递归的 统计链表中所有节点的 cost 和。(图中的 otherOperation 即为它指向的下一个 装饰器的 operation)
小结
组合模式更像是一个树状的层次结构,也是递归的形式统计。而装饰模式更像是一个链式结构,只不过它一定会有基础节点,否就违背了装饰的本意。 感觉这个和桥接模式也很像,但是区别在于,桥接模式没有串联递归,例如桥接的 咖啡豆+甜度组合,像是 选择+组合问题,但是桥接模式就 类似 无语义重叠的集合问题 例如 咖啡豆组合 + 牛奶 + 糖 + … 同样都是解决静态继承很繁杂的问题,并采用动态的方式来结合。
外观模式
概述
就是将 子类 的所有类似功能的接口统一,然后使用一个外观对象 对他们进行统一调用,这样简化了客户端的操作。例如 睡觉前要 关电视 关灯 关空调等操作,客户一个个操作很麻烦,就将他们的开闭都统一成一个相同的接口,然后用一个遥控器统一完成他们的开闭操作。用户就只用对遥控器发送一次指令,然后由遥控器分别操作其他子类。
享元模式
概述
利用共享的方式来支持大量细粒度的对象,这些对象一部分内部状态是相同的。 当每个对象中存在大部分相同的共享状态时,就将这些相同的状态(内部状态,不会随着环境改变而改变)提取出来放在 池 中,以减少不必要的内存占用。(共享技术)
结构
- Contex 情景:可以类比为强站游戏中每个 子弹的原始对象,它包含颜色,大小,位置等属性。将他们分为外部状态 即子弹的位置,这是会随时间改变的不共享,和内部状态,即不变的属性,将这些内部属性包装为 Flyweight 结构。
- Flyweight:将共享状态包装进这个类中,即参数 repeatState
- FlyweightFactory : 工厂类,即内存池的实现吧,将 相同的状态封装为一个 享元类,并用 map 结构保存。
综上,首先 将共享状态封装为 Flyweight 对象,然后用 FlyweightFactory 对象实现一个内存池,存储所有共享状态。如果要根据内部状态外部状态初始化一个原始对象,外部状态作为对象的私有参数开辟内存存储,内部状态从工厂中获取,工厂返回给它一个 Flyweight 对象 的地址。要获取当前对象的所有状态,根据Flyweight 中开辟的接口获取即可。
代理
概述
控制对其他对象的访问。
结构
将原始服务对象 使用一个新的类(代理)对他进行封装,客户通过代理的接口间接地对原始服务端访问。
为什么要增加代理呢?
- 虚拟代理:对于原始服务对象 很大且使用频率不高的服务对象,使用代理可以将原始对象的延迟初始化到请求时再执行,并且代理可以自动根据当前客户的使用服务器的频率选择释放代理。同样的也能延迟访问 ,例如一个加载很慢的图片,可以由虚拟代理先获得图像大小信息生成一个临时图片代替原始图片。
- 远程代理:就是将访问远程服务器的复杂地址请求逻辑由代理 封装,像不同的地址空间发送请求。
- 保护代理:可以根据客户端的权限,由代理选择给哪些客户端访问服务器的权限
- 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它….以及缓存一下重复的请求结果减少重复访问。