设计模式-结构型模式
声明:文章内容绝大多数来自于南京大学 计算机科学与技术系 面向对象设计方法 课程课件
结构型模式
- 结构型模式(Structural Pattern)关注如何将现有类或对象组织在一起形成更加强大的结构
- 不同的结构型模式从不同的角度组合类或对象,它们在尽可能满足各种面向对象设计原则的同时为类或对象的组合提供一系列巧妙的解决方案
类结构型模式
- 关心类的组合,由多个类组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系
对象结构型模式
- 关心类与对象的组合,通过关联关系,在一个类中定义另一个类的实例对象,然后通过该对象调用相应的方法
Adapter Pattern 适配器模式
适配器模式:将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。
又名:包装器(Wrapper)模式
定义中所提及的接口是指广义的接口,它可以表示一个方法或者方法的集合
适配器模式的结构(类适配器)
Adapter适配器同时是Target和Adaptee的子类,Target通过调用Adatper来获取Adaptee的功能,Target为接口。
1 | package adapter; |
适配器模式的结构(对象适配器)
Adapter维护了一个Adaptee的对象,并继承自Target。
1 | package adapter; |
缺省适配器模式(Default Adapter Pattern)
当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。
模式优点
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构
- 增加了类的透明性和复用性,提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用
- 灵活性和扩展性非常好
- 类适配器模式:置换一些适配者的方法很方便
- 对象适配器模式:可以把多个不同的适配者适配到同一个目标,还可以适配一个适配者的子类
模式缺点
- 类适配器模式:
- (1) 一次最多只能适配一个适配者类,不能同时适配多个适配者;
- (2) 适配者类不能为最终类;
- (3) 目标抽象类只能为接口,不能为类
- 对象适配器模式:
- 在适配器中置换适配者类的某些方法比较麻烦
适用环境
- 模式适用环境
- 系统需要使用一些现有的类,而这些类的接口不符合系统的需要,甚至没有这些类的源代码
- 创建一个可以重复使用的类,用于和一些彼此之间没有太大关联的类,包括一些可能在将来引进的类一起工作
Bridge Pattern 桥接模式
桥接模式:将抽象部分与它的实现部分解耦,使得两者都能够独立变化。
- 桥接模式的定义
- 又被称为柄体(Handle and Body)模式或接口(Interface)模式
- 用抽象关联取代了传统的多层继承
- 将类之间的静态继承关系转换为动态的对象组合关系
1 | package bridge; |
模式优点
- 分离抽象接口及其实现部分
- 可以取代多层继承方案,极大地减少了子类的个数
- 提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,不需要修改原有系统,符合开闭原则
模式缺点
- 会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程
- 正确识别出系统中两个独立变化的维度并不是一件容易的事情
模式适用环境
- 需要在抽象化和具体化之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系
- 抽象部分和实现部分可以以继承的方式独立扩展而互不影响
- 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立地进行扩展
- 不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统
Composite Pattern 组合模式
组合模式通过一种巧妙的设计方案使得用户可以一致性的处理整个树形结构或者树形结构的一部分,他描述了如何将容器和叶子对象进行地柜组合,是的用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象。
组合模式:组合多个对象形成树形结构以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象。
- 又称为“部分-整体”(Part-Whole)模式
- 将对象组织到树形结构中,可以用来描述整体与部分的关系
- 组合模式的结构
1 | public class CompositePattern { |
- 透明组合模式
- 安全组合模式
模式优点
- 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,让客户端忽略了层次的差异,方便对整个层次结构进行控制
- 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码
- 增加新的容器构件和叶子构件都很方便,符合开闭原则
- 为树形结构的面向对象实现提供了一种灵活的解决方案
模式缺点
- 在增加新构件时很难对容器中的构件类型进行限制
模式适用环境
- 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们
- 在一个使用面向对象语言开发的系统中需要处理一个树形结构
- 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型
Decorator Pattern 装饰模式
- 装饰模式分析
- 可以在不改变一个对象本身功能的基础上给对象增加额外的新行为
- 是一种用于替代继承的技术,它通过一种无须定义子类的方式给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系
- 引入了装饰类,在装饰类中既可以调用待装饰的原有类的方法,还可以增加新的方法,以扩展原有类的功能
装饰模式:动态的给一个对象增加一些额外的职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。
- 装饰模式的定义:
- 以对客户透明的方式动态地给一个对象附加上更多的责任
- 可以在不需要创建更多子类的情况下,让对象的功能得以扩展
ConcreteComponent为具体构建类,Component为抽象构建类。Decorator为抽象装饰角色,ConcreteDecorateA,ConcreteDecorateB为具体装饰角色。
通过对operation方法中代码的修改(调用父类的operation并添加功能),达到装饰器的目的。
1 | package decorator; |
模式优点
- 对于扩展一个对象的功能,装饰模式比继承更加灵活,不会导致类的个数急剧增加
- 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为
- 可以对一个对象进行多次装饰
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,且原有类库代码无须改变,符合开闭原则
模式缺点
- 使用装饰模式进行系统设计时将产生很多小对象,大量小对象的产生势必会占用更多的系统资源,在一定程度上影响程序的性能
- 比继承更加易于出错,排错也更困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐
模式适用环境
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
- 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式】
Facade Pattern 外观模式
- 分析
- 一个客户类需要和多个业务类交互,而这些需要交互的业务类经常会作为一个整体出现
- 引入一个新的外观类(Facade)来负责和多个业务类【子系统(Subsystem)】进行交互,而客户类只需与外观类交互
- 为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互
- 没有外观类:每个客户类需要和多个子系统之间进行复杂的交互,系统的耦合度将很大
- 引入外观类:客户类只需要直接与外观类交互,客户类与子系统之间原有的复杂引用关系由外观类来实现,从而降低了系统的耦合度
外观模式:为子系统中的一组接口提供一个统一的入口,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
- 外观模式的定义
- 又称为门面模式
- 是迪米特法则的一种具体实现
- 通过引入一个新的外观角色来降低原有系统的复杂度,同时降低客户类与子系统的耦合度
- 所指的子系统是一个广义的概念,它可以是一个类、一个功能模块、系统的一个组成部分或者一个完整的系统
- 外观模式的结构
1 | public class SubSystemA { |
模式优点
- 它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易
- 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可
- 一个子系统的修改对其他子系统没有任何影响,而且子系统的内部变化也不会影响到外观对象
模式缺点
- 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性
- 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则
模式适用环境
- 要为访问一系列复杂的子系统提供一个简单入口
- 客户端程序与多个子系统之间存在很大的依赖性
- 在层次化结构中,可以使用外观模式的定义系统中每一层的入口,层与层之间不直接产生联系,而是通过外观类建立联系,降低层之间的耦合度
Flyweight Pattern 享元模式
享元模式:通过共享技术实现相同或相似对象的重用
享元池(Flyweight Pool):存储共享实例对象的地方
内部状态(Intrinsic State):存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享(例如:字符的内容)
外部状态(Extrinsic State):随环境改变而改变的、不可以共享的状态。享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的(例如:字符的颜色和大小)
享元模式:运用共享技术有效的支持大量细粒度对象的复用。
- 又称为轻量级模式
- 要求能够被共享的对象必须是细粒度对象
享元模式的结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39public abstract class Flyweight {
public abstract void operation(String extrinsicState);
}
public class ConcreteFlyweight extends Flyweight {
// 内部状态intrinsicState作为成员变量,同一个享元对象其内部状态是一致的
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
public void operation(String extrinsicState) {
// 实现业务方法
}
}
public class UnsharedConcreteFlyweight extends Flyweight {
public void operation(String extrinsicState) {
// 实现业务方法
}
}
public class FlyweightFactory {
private HashMap<String, Flyweight> flyweights = new HashMap();
public Flyweight getFlyweight(String key) {
if (flyweights.containsKey(key)) {
return flyweights.get(key);
} else {
Flyweight flyweight = new ConcreteFlyweight();
flyweights.put(flyweight);
return flyweight;
}
}
}
Flyweight(抽象享元类)
ConcreteFlyweight(具体享元类)
UnsharedConcreteFlyweight(非共享具体享元类)
FlyweightFactory(享元工厂类)
模式优点
可以减少内存中对象的数量,使得相同或者相似的对象在内存中只保存一份,从而可以节约系统资源,提高系统性能
外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享
模式缺点
使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化
为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长
模式适用环境
一个系统有大量相同或者相似的对象,造成内存的大量耗费
对象的大部分状态都可以外部化,可以将这些外部状态传入对象中
在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,在需要多次重复使用享元对象时才值得使用享元模式
Proxy Pattern 代理模式
代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
代理模式的定义
引入一个新的**代理对象**
代理对象在客户端对象和目标对象之间起到中介的作用
去掉客户不能看到的内容和服务或者增添客户需要的额外的新服务
代理模式的结构
1 | public abstract class Subject { |
- 常见的代理模式
- **远程代理(Remote Proxy)**:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在同一台主机中,也可以在另一台主机中,远程代理又称为大使(Ambassador)
- **虚拟代理(Virtual Proxy)**:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建
- **保护代理(Protect Proxy)**:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限
- **缓冲代理(Cache Proxy)**:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果
- **智能引用代理(Smart Reference Proxy)**:当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等
远程代理
客户端程序可以访问在远程主机上的对象,远程主机可能具有更好的计算性能与处理速度,可以快速地响应并处理客户端的请求
可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在
客户端完全可以认为被代理的远程业务对象是在本地而不是在远程,而远程代理对象承担了大部分的网络通信工作,并负责对远程业务方法的调用
虚拟代理
对于一些占用系统资源较多或者加载时间较长的对象,可以给这些对象提供一个虚拟代理
在真实对象创建成功之前虚拟代理扮演真实对象的替身,而当真实对象创建之后,虚拟代理将用户的请求转发给真实对象
使用一个“虚假”的代理对象来代表真实对象,通过代理对象来间接引用真实对象,可以在一定程度上提高系统的性能
例如快捷方式
由于对象本身的复杂性或者网络等原因导致一个对象需要较长的加载时间,此时可以用一个加载时间相对较短的代理对象来代表真实对象(结合多线程技术)
一个对象的加载十分耗费系统资源,让那些占用大量内存或处理起来非常复杂的对象推迟到使用它们的时候才创建,而在此之前用一个相对来说占用资源较少的代理对象来代表真实对象,再通过代理对象来引用真实对象(用时间换取空间)
Java动态代理
以下是一个通过java动态代理实现保护代理模式的实例
1 | public interface Person { |
其中Person为Subject接口,PersonImpl为Person接口的具体实现。
OwnerInvocvationHandler和NotOwnerInvocvationHandler为两种不同的代理。
MyDynamicProxyClient为调用和测试类。
模式优点
能够协调调用者和被调用者,在一定程度上降低了系统的耦合度
客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性
远程代理:可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高了系统的整体运行效率
虚拟代理:通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销
缓冲代理:为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间
保护代理:可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限
模式缺点
由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢(例如保护代理)
实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂(例如远程代理)
模式适用环境
当客户端对象需要访问远程主机中的对象时可以使用远程代理
当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理
当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理
当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用保护代理
当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能引用代理
代理模式和适配器模式、装饰器模式的比较
•Adapter模式中适配器为它所适配的对象提供了一个不同的接口,而Proxy则提供了与它的实体相同的接口,或其接口的子集(Protection Proxy可能会拒绝执行实体的操作)
•Decorator的实现和Proxy类似,但目的不同:Decorator对象添加一个或多个功能,而Proxy则控制对对象的访问