目录
☞ 23 种设计模式——结构型设计模式(7种)
☞ 23 种设计模式——行为型设计模式(11种)
3. 结构型设计模式
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者采用组合或聚合组合对象。
由于组合关系或聚合关系比较继承关系耦合度低,满足“合成复合原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。
结构型模式分为以下 7 种:
1)代理(Proxy)模式:为某对象提供一种代理以控制对象的访问。即客户端通过代理简介地访问该对象,从而限制、增强或修改该对象的一些特征。
2)适配器(Adapter)模式:将一个类的接口转换成希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
3)桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
4)装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能。
5)外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
6)享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
3.1 代理(Proxy)模式
在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点购买。又如找女朋友、找保姆、找工作都可以通过中介完成。
在软件设计中,使用代理模式的例子很多,如,要访问原型对象比较大(如视频或者大图像等),其下载要花很多时间。还有因为安全需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。
3.1.1 代理模式的定义与特点
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不合适或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
代理模式的主要优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理模式可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度。
其主要缺点是:
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度。
3.1.2 代理模式的结构与实现
代理模式的结构比较简单,只要是通过定义一个集成抽象主题的代理来包含真实主题,从而实现对真实主题访问,下面来分析基本结构和实现方法。
(1)模式的结构
代理模式的主要角色如下:
1)抽象主题类:通过接口或抽象类声明真实主题或代理对象实现的业务方法。
2)真实主题类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要 引用的对象。
3)代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
其结构图如图3-1所以:
图3-1 代理模式的结构图
(2)模式的实现
代理模式的实现代码如下:
1 // 抽象主题 2 interface Subject { 3 void request(); 4 }
1 // 真实主题 2 class RealSubject implements Subject { 3 public void request() { 4 System.out.println("访问真实主题方法..."); 5 } 6 }
1 // 代理 2 class Proxy implements Subject { 3 private RealSubject realSubject; 4 public void request() { 5 if (realSubject == null) { 6 realSubject = new RealSubject(); 7 } 8 preRequest(); 9 realSubject.request(); 10 postRequest(); 11 } 12 public void preRequest() { 13 System.out.println("访问真实主题之前的预处理。"); 14 } 15 public void postRequest() { 16 System.out.println("访问真实主题之后的后续处理。"); 17 } 18 }
1 public class ProxyTest { 2 public static void main(String[] args) { 3 Proxy proxy = new Proxy(); 4 proxy.Request(); 5 } 6 }
程序运行的结果如下:
访问真实主题之前的预处理。 访问真实主题方法... 访问真实主题之后的后续处理。
3.1.3 代理模式的应用场景
前面分析了代理模式的结构与特点,现在分析以下的应用 场景 。
● 远程代理,这种方法通常是为了隐藏目标对象目标存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
● 虚拟代理,这种方式通常用于要创建的目标对象开销很大。例如,下载一个很大的图片需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
● 安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
● 智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
● 延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。
3.1.4 代理模式的扩展
在前面介绍的代理模式中,代理类中包含了对真实主题引用,这种方式存在两个缺点。
1)真实主题与代理主题一一对应,增加真实主题也要增加代理。
2)设计代理以前真实主题必须事先存在,不太灵活。采用动态代理模式可以解决以上问题,如 SpringAOP,其结构图如图3-2所示。
3.2 适配器(Adapter)模式
在现实生活中,经常出现两个对象因接口不兼容而不能再一起工作的实例,这时需要第三者进行适配。例如,讲中文的人同讲英文的人对话时需要一个翻译,用直流电的笔记本电脑接交流电源时需要一个电源适配器。
在软件设计中也可能出现:需要开发的具有某种业务功能的组件在现有的组件库中已经存在,但它们与当前系统的接口规范不兼容,如果重写开发这些组件成本又很高,这时用是适配器模式能很好地解决这些问题。
3.2.1 模式的定义与特点
适配器模式的定义如下:将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起功能的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者之间的耦合度比后者高,其要求程序员了解现有组件库中的相关的内部结构,所以应对相对较少些。
该模式的主要优点如下:
● 客户端通过适配器可以透明地调用目标接口。
● 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
● 将目标和适配这类解耦,解决了目标类和适配类接口不一致问题。
其缺点是:
对类适配器来说,更换适配器的实现过程比较复杂。
3.2.2 模式的结构与实现
类适配器模式可采用充多充继承方式实现,如 C++ 可以定义一个适配器类来同时继承房钱系统的业务接口和现有组件库中已经存在的组件接口;Java 不支持多继承,但可以定义一个适配器来来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
对象适配器模式可采用将现有组件库中已经实现的组件引入适配器类,该类同时实现当前系统的业务接口。现在来介绍他们的基本结构。
(1)模式的结构
适配器模式包含以下主要角色。
1)目标接口:当前系统业务所期待的接口,它可以是抽象类或接口。
2)适配类:它是被访问和适配的现存组件库中的组件接口。
3)适配器类:它是一个转换器,通过继承或引用适配者的对象,把适配器接口转换成目标接口,让客户目标接口的可是访问适配者。
类适配器模式的结构图,如图3-2所示:
3-2 类适配器模式的结构图
对象适配器模式的结构图,如图3-3所示:
图3-3对象适配器模式的结构图
(2)模式的实现
1 // 目标接口 2 interface Target { 3 public void request(); 4 }
① 类适配器模式的代码如下:
1 // 适配者接口 2 class Adaptee { 3 public void specificRequest() { 4 System.out.println("适配者中的业务代码被调用!"); 5 } 6 } 7 8 // 类适配器类 9 class ClassAdapter extends Adaptee implements Target { 10 public void request() { 11 specificRequest(); 12 } 13 }
1 //客户端代码 2 public class ClassAdapterTest { 3 public static void main(String[] args) { 4 System.out.println("类适配器模式测试:"); 5 Target target = new ClassAdapter(); 6 target.request(); 7 } 8 }
程序运行结果如下:
类适配器模式测试: 适配者中的业务代码被调用!
② 对象适配器模式的代码如下:
1 // 对象适配器类 2 class ObjectAdapter implements Target { 3 private Adaptee adaptee; 4 public ObjectAdapter(Adaptee adaptee) { 5 this.adaptee = adaptee; 6 } 7 public void request() { 8 adaptee.specificRequest(); 9 } 10 }
1 // 客户端代码 2 public class ObjectAdapterTest { 3 public static void main(String[] args) { 4 System.out.println("对象适配器模式测试:"); 5 Adaptee adaptee = new Adaptee(); 6 Target target = new ObjectAdapter(adaptee); 7 target.request(); 8 } 9 }
程序运行结果如下:
对象适配器模式测试: 适配者中的业务代码被调用!
说明:对象适配器模式中的“目标接口”和“适配者类”的代码同类适配器模式一样,只要修改适配器类和客户端的代码即可。
3.2.3 模式的应用实例
【例】用适配器模式(Adapter)模式新能源汽车的发动机。
分析:新能源汽车的发动机有电能发动机和光能发动机等,各种发动机的驱动,例如,电能发动机的驱动方法 electricDrive() 是用电能驱动,而光能发动机的驱动方法 opticalDrice() 用光能驱动,它们是适配器模式中被访问的适配器。
客户端希望用统一的发动机驱动方法 drive() 访问这两种发动机,所以必须定义一个统一的目标接口 Motor,然后再定义电能适配器(Electric Adapter)和光能适配器(Optical Adapter)去适配这两种发动机。结构图如图3-4所示。
图3-4 发动机适配器的结构图
程序代码如下:
1 // 目标:发动机 2 interface Motor { 3 public void drive(); 4 } 5 6 // 适配者1:电能发动机 7 class ElectricMotor { 8 public void electricDrive() 9 { 10 System.out.println("电能发动机驱动汽车!"); 11 } 12 } 13 14 // 适配者2:光能发动机 15 class OpticalMotor { 16 public void opticalDrive() { 17 System.out.println("光能发动机驱动汽车!"); 18 } 19 } 20 21 // 电能适配器 22 class ElectricAdapter implements Motor { 23 private ElectricMotor emotor; 24 public ElectricAdapter() { 25 emotor = new ElectricMotor(); 26 } 27 public void drive() { 28 emotor.electricDrive(); 29 } 30 } 31 32 // 光能适配器 33 class OpticalAdapter implements Motor { 34 private OpticalMotor omotor; 35 public OpticalAdapter() { 36 omotor = new OpticalMotor(); 37 } 38 public void drive() { 39 omotor.opticalDrive(); 40 } 41 }1 public class MotorAdapterTest { 2 public static void main(String[] args) { 3 Motor mEle = new ElectricAdapter(); 4 mEle.drive(); 5 6 Motor mOpt = new OpticalAdapter(); 7 mOpt.drive(); 8 } 9 }程序运行结果:
电能发动机驱动汽车! 光能发动机驱动汽车!
3.2.4 模式的应用场景
适配器模式(Adapter)通常适用于以下场景:
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
3.4.5 模式的扩展
适配器模式可扩展为双向适配器模式,双向适配器类既可以把适配者接口转换成目标接口,也可以吧目标接口转换成适配者接口,其结构图如图3-5所示。
图3-5 双向适配器模式的结构图
程序代码如下&