结构型模式
结构型模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构。它分为类结构型模式和对象结构型模式。
- 类结构性模式:采用继承机制来组织接口和类。
- 对象结构型模式:釆用组合或聚合来组合对象。
代理模式
概述
某些情况客户不想或者不能直接访问到目标,此时,可通过“代理”的方式引入第三方中间者,通过代理间接地访问到目标。
代理模式(Proxy Pattern) :给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式包含的角色如下:
- 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
示例
以前交通、互联网没那么发达,购买火车票可以在火车站直接购买,但通常人们是在车票代售点进行购买。这个例子中火车站是目标对象,代售点是代理对象。
【示例代码】
优缺点
优点:
- 将客户端与目标对象分离,一定程度上降低耦合。
- 可以控制对真实对象的使用权限。
- 可对代理对象进行功能扩展(如进行过滤)。
缺点:
- 增加复杂度。
- 客户端与目标之间增加了代理,可能会导致处理速度变慢。
适配器模式
概述
适配器模式(Adapter Pattern):将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配器模式包含以下角色:
- Client :客户。
- Target :目标抽象类(接口),客户侧所感知的接口,客户所期待的接口。
- Adaptee :适配者类,需要进行适配的组件,可理解为已存在的组件,客户无感知。
- Adapter :适配器类,通过继承或引用适配者的对象,把Adaptee组件接口转换成Target目标接口,让客户按目标接口的格式访问适配者。
分类
适配器模式可分为对象适配器和类适配器。
-
对象适配器 :适配器实现了其中一个对象的接口, 并对另一个对象进行封装。
-
类适配器:适配器同时继承两个对象的接口。仅能在支持多重继承的编程语言中实现, 例如 C++。
类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用。
示例
现有电脑仅有SD卡读写接口,而数据存储使用TF卡,此时就需要一个适配器(转接器),使得能通过电脑上仅有的SD卡接口实现对TF卡的读写。
类适配器(多继承)
【示例代码】
对象适配器
对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。
【示例代码】
装饰者模式
概述
装饰模式(Decorator Pattern)在不改变现有类(封闭-开放原则)和不使用继承(类爆炸)的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
装饰者模式中包含以下角色:
- 抽象构件(Component)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(Concrete Component)角色 :实现抽象构件,将被装饰的对象。
- 抽象装饰(Decorator)角色 : 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(Concrete Decorator)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
示例
【问题描述】
现在有一个咖啡店售卖多种单品咖啡(Espresso、ShortBlack等)销售火爆,店家推出组合咖啡(单品咖啡+配料),组合很多,如:单品咖啡+牛奶,单品咖啡+糖 等等。现需要更新系统支持组合咖啡价格计算。
【问题分析】
-
方案一
在原单品咖啡类中加入添加组合调料的功能,可选择是否添加任意调料。
该方案新增或者删除调料时,需要修改咖啡类中的内容,违反了开放-封闭原则(OCP)。
-
方案二
创建一个抽象类Coffee,让单品咖啡和所有的组合咖啡继承Coffee类。
该方案新增加一个单品咖啡或者调料,类的数量就会倍增,产生类爆炸。
-
方案三(装饰者模式)
- 创建咖啡抽象类Coffee。【抽象构件】
- 创建个单品咖啡,让单品咖啡继承Coffee。【具体构件】
- 创建组合咖啡抽象基类CoffeeDecorator,包含Coffee指针(引用)类型的成员变量。【抽象装饰】
- 创建各种加调料的组合咖啡装饰类,都继承自组合咖啡抽象基类CoffeeDecorator。【具体装饰】
【示例代码】
桥接模式
概述
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使他们都能独立的变化。简单理解,在有多个变化因素的情况下,减少使用继承(降低耦合),采用合成/聚合的方式实现。
桥接模式包含以下角色:
- 抽象角色(Abstraction) :抽象类,定义抽象角色的接口,并维护指向实现接口角色的引用(或指针)。
- 扩充抽象角色(Refined Abstraction) :扩充抽象类,扩充由抽象角色定义的接口,调用在 “实现接口类” 中定义的业务接口。
- 实现接口角色(Implementor) :实现接口类,定义实现角色的接口。
- 具体实现角色(Concrete Implementor) :具体实现类,实现 “实现接口类” 中定义的接口方法。
示例
【问题描述】
现需设计实现一个视频播放器,有多个版本,分别支持不同的操作系统、解码不同的视频格式。如:Windows系统+AVI视频格式,Mac系统+RMVB视频格式等。
【问题分析】
显然该案例中,变化因素有两个:操作系统和解码器。
-
方案一:继承
使用继承等方式实现,有Windows-AVI播放器,Windows-RMVB播放器,Mac-AVI播放器等。若是增加一种操作系统或增加一种视频格式,就需要创建更多的类。(类爆炸)
-
方案二:桥接模式
其中角色设计如下:
- 抽象角色:操作系统,包含一个解码器,定义视频播放接口。
- 扩充抽象角色:具体操作系统,Windows、Mac等。
- 实现接口角色:解码器,定义解码视频文件接口。
- 具体实现角色:具体解码器,实习具体格式文件解码。
【示例代码】
优缺点
桥接模式优点:
- 接口和实现分离,可对客户影藏实现细节。
- 提高了系统的可扩展性,对两个变化维度中的任意一个进行扩展,都不需要修改原系统。
桥接模式缺点:
- 增加系统理解和设计难度
外观模式
概述
外观模式(Facade Pattern):通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。外观模式又称为门面模式,它是一种对象结构型模式。外观模式是 迪米特法则 的典型应用。
外观模式包含以下角色:
- 外观角色(Facade):为复杂的子系统提供一个统一的接口,面向客户端。
- 子系统角色(Sub System):系统实现的细节,客户不关注这些细节,客户通过外观角色对子系统进行访问。
示例
【场景描述】
小明每天作息很规律,其中包括以下场景操作:
- 下班回家:开灯、打开空调、打开电视;
- 睡觉:关电视、关灯;
小明购买了一个天猫精灵,通过语言控制这些设备开启关闭。设计类图如下:
【示例代码】
优缺点
优点:
- 降低了客户端和子系统间的耦合度,子系统的变化不会影响到客户端的调用。
- 对客户端屏蔽了子系统的组件细节,减少了客户端处理的对象数量。
缺点:
- 违背了 “开放-封闭原则”,不使用抽象外观的情况下,新增子系统可能需要修改外观类或客户端的源代码。
组合模式
概述
组合模式(Composite Pattern),又称为整体-部分(Part-Whole)模式,将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使用户对单个对象和组合对象的使用具有一致性。
组合模式包括以下角色:
- 抽象部件(Component):主要作用是为叶子和容器部件声明公共接口,适当情况下实现它们的默认行为。
- 容器部件(Composite):主要作用是存储和管理子部件,实现与子部件有关操作,如增加(add)和删除(remove)等。是树形结构中的分支节点。
- 叶子(Leaf):叶子结点没有子结点。
分类:
- 透明组合模式:抽象部件中声明了所有用于管理成员对象的方法。叶子中可能某些操作无意义或者应该被禁止。
- 安全组合模式:抽象部件中 没有 声明了所有用于管理成员对象的方法。此时,容器部件和叶子是有区别的,客户必须区别对待它们。
示例
【场景描述】
实现一个文件管理工具,支持对文件夹和文件的创建和删除功能,并且提供 tree 接口显示文件系统结构。设计类图如下:
【示例代码】
享元模式
概述
享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。享元模式中存在两种状态:
- 内部状态:不会随着环境的改变而改变的可共享部分。
- 外部状态:随环境改变而改变的不可以共享的部分。
享元模式包括以下角色:
- 抽象享元(Flyweight)角色:抽象类或接口,声明了具体享元类公共的方法。
- 具体享元(Concrete Flyweight):实现了抽象享元的接口方法,称为享元对象,为内部状态。
- 非共享具体享元(Unshared Concrete Flyweight):不进行共享的Flyweight子类,为外部状态。
- 享元工厂(Flyweight Factory):用来创建和管理Flyweight对象,提供给客户使用。
示例
【场景描述】
俄罗斯方块中每次产生的方块只有几种形状(L、I、T、O),如果每个不同的方块都是一个实例对象,这些对象就要占用很多的内存空间,此时适合利用享元模式进行实现。类图设计如下:
【示例代码】
优缺点
【优点】
- 减少内存中相同或相似对象的数量,节约系统资源。
【缺点】
- 将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂。
参考:
- 《大话设计模式》 程杰
- 黑马程序员Java设计模式详解,全网最全23种Java设计*