本人学习笔记。内容参考《Java设计模式》刘伟 教程配套PPT。仅供学习参考使用。
文章目录
总述
结构型模式关注如何将现有类或对象组织在一起形成更加强大的结构。
不同的结构型模式从不同的角度组合类或对象,它们在尽可能满足各种面向对象设计原则的同时为类或对象的组合提供一系列巧妙的解决方案。
类结构型模式:关心类的组合,由多个类组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。
对象结构型模式:关心类与对象的组合,通过关联关系,在一个类中定义另一个类的实例对象,然后通过该对象调用相应的方法。
结构型模式有7种,下面的表格我总结了它们的定义和例子:
模式名称 | 定 义 | 形象化例子 | 应用实例 |
---|---|---|---|
适配器模式 | 将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。 | 电源适配器 | 重用灯光声音设计玩具车 |
桥接模式 | 将抽象部分与它的实现部分解耦,使得两者都能够独立变化。 | 毛笔颜色和型号分离 | 跨平台图像浏览系统 |
组合模式 | 组合多个对象形成树形结构,以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象。 | 操作系统目录结构 | 可杀毒不同文件的杀毒软件 |
装饰模式 | 动态地给一个对象增加一些额外的职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。 | 图片加边框 | 图形界面构件库 |
外观模式 | 为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 | 去茶馆喝茶 | 文件加密(包含读写) |
享元模式 | 运用共享技术有效地支持大量细粒度对象的复用。 | 活字印刷 | 围棋软件的棋子对象 |
代理模式 | 给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。 | 代购 | 信息查询系统 |
1.适配器模式
概述
将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。这里的接口是广义的接口,可以表示一个方法或者方法的集合。
典型例子:电源适配器、重用灯光、声音的代码设计新的玩具车
结构
包括:Target(目标抽象类)Adapter(适配器类)Adaptee(适配者类)
类适配器的结构:
对象适配器的结构:
缺省适配器模式的结构:(不使用接口中所有方法的情况)
双向适配器模式的结构:
优缺点
优点:
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
- 增加了类的透明性和复用性,提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
- 灵活性和扩展性非常好。
- 类适配器模式:置换一些适配者的方法很方便。
- 对象适配器模式:可以把多个不同的适配者适配到同一个目标,还可以适配一个适配者的子类。
缺点:
- 类适配器模式:一次最多只能适配一个适配者类,不能同时适配多个适配者;适配者类不能为最终类;目标抽象类只能为接口,不能为类。
- 对象适配器模式:在适配器中置换适配者类的某些方法比较麻烦。
2.桥接模式
概述
将抽象部分与它的实现部分解耦,使得两者都能够独立变化。
用抽象关联取代了传统的多层继承。
将类之间的静态继承关系转换为动态的对象组合关系。
典型例子:毛笔(颜色和型号分离)、跨平台图像浏览系统。
结构
桥接模式包含:Abstraction(抽象类)RefinedAbstraction(扩充抽象类)Implementor(实现类接口)ConcreteImplementor(具体实现类)
以跨平台图像浏览系统为例:
优缺点
优点:
- 分离抽象接口及其实现部分。
- 可以取代多层继承方案,极大地减少了子类的个数。
- 提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,不需要修改原有系统,符合开闭原则。
缺点:
- 会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程。
- 正确识别出系统中两个独立变化的维度并不容易。
3.组合模式
概述
组合模式通过一种巧妙的设计方案使得用户可以一致性地处理整个树形结构或者树形结构的一部分,它描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象。
“组合”这个名字不太好记,它的含义是:组合多个对象形成树形结构以表示具有部分-整体关系的层次结构。
经典例子:操作系统目录结构、能为不同类型的文件提供不同的杀毒方式的杀毒软件
结构
组合模式包含:Component(抽象构件)Leaf(叶子构件)Composite(容器构件)
优缺点
优点:
- 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,让客户端忽略了层次的差异,方便对整个层次结构进行控制。
- 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
- 增加新的容器构件和叶子构件都很方便,符合开闭原则。
- 为树形结构的面向对象实现提供了一种灵活的解决方案。
缺点:
- 在增加新构件时很难对容器中的构件类型进行限制。
4.装饰模式
概述
动态地给一个对象增加一些额外的职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。
可以在不改变一个对象本身功能的基础上给对象增加额外的新行为。引入了装饰类,在装饰类中既可以调用待装饰的原有类的方法,还可以增加新的方法,以扩展原有类的功能。
是一种用于替代继承的技术,它通过一种无须定义子类的方式给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系。
经典例子:图片加边框(可嵌套)、图形界面构件库(如带滚动条的窗体、带黑色边框的文本框)
结构
装饰模式包含:Component(抽象构件)ConcreteComponent(具体构件)Decorator(抽象装饰类)ConcreteDecorator(具体装饰类)
例如创建一个带黑框带滑条的窗口,在client中只需:
component = new Window();
componentSB = new ScrollBarDecorator(component); //体会一下聚合关系
componentBB = new BlackBorderDecorator(componentSB);
优缺点
优点:
- 对于扩展一个对象的功能,装饰模式比继承更加灵活,不会导致类的个数急剧增加。
- 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为。
- 可以对一个对象进行多次装饰。
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,且原有类库代码无须改变,符合开闭原则。
缺点:
- 使用装饰模式进行系统设计时将产生很多小对象,大量小对象的产生势必会占用更多的系统资源,在一定程度上影响程序的性能。
- 比继承更加易于出错,排错也更困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
5.外观模式
概述
为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
引入一个新的外观类来负责和多个业务类(子系统)进行交互,而客户类只需与外观类交互。从而降低原有系统的复杂度,同时降低客户类与子系统的耦合度。
是迪米特法则的一种具体实现。(复习设计原则:每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位)
典型例子:茶馆服务员泡茶、文件加密模块(读取源文件、加密、保存加密之后的文件)
结构
外观模式包含:Facade(外观角色)SubSystem(子系统角色)
为使得增加、删除或更换与外观类交互的子系统类时不用修改原来的代码,可增加抽象外观类:
优缺点
优点:
- 它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。
- 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可。
- 一个子系统的修改对其他子系统没有任何影响,而且子系统的内部变化也不会影响到外观对象。
缺点:
- 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性。
- 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则(可增加抽象外观类解决)。
6.享元模式
概述
运用共享技术有效地支持大量细粒度对象的复用。
- 将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的。
- 需要的时候将对象从享元池中取出,即可实现对象的复用。
- 通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份。
经典例子:字符享元(活字印刷)、围棋软件的棋子对象
结构
享元模式包含:Flyweight(抽象享元类)ConcreteFlyweight(具体享元类)UnsharedConcreteFlyweight(非共享具体享元类)FlyweightFactory(享元工厂类)
优缺点
优点:
- 可以减少内存中对象的数量,使得相同或者相似的对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。
- 外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
缺点:
- 使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
- 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。
7.代理模式
概述
给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
代理对象在客户端对象和目标对象之间起到中介的作用。去掉客户不能看到的内容和服务或者增添客户需要的额外的新服务。
经典例子:代购、信息查询系统
不同的代理模式:
- 远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在同一台主机中,也可以在另一台主机中。
- 虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。(如文件的快捷方式)
- 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
- 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
- 智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。
- 动态代理(Dynamic Proxy):可以让系统在运行时根据实际需要来动态创建代理类,让同一个代理类能够代理多个不同的真实主题类而且可以代理不同的方法。
结构
代理模式包含:Subject(抽象主题角色)Proxy(代理主题角色)RealSubject(真实主题角色)
在代理对象Proxy中,preRequest方法做的是请求前的工作(如身份验证),postRequest方法做的是请求后的工作(如记录信息)。
优缺点
优点:
- 能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
- 客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
缺点:
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢(例如保护代理)。
- 实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂(例如远程代理)。