设计模式 —— 结构型模式
结构型模式(structural pattern)关注如何将现有类或对象组织在一起形成更加强大的结构
可分为两种:
- 类结构型模式:关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系
- 对象结构型模式:关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。更符合“合成复用原则”
1. 适配器模式(adapter pattern)
1.1 定义
"convert the interface of a class into another interface clients expect. adapter lets classes work together that couldn't otherwise because of incompatible interfaces."
将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作
又称包装器(wrapper),既可以作为类结构型模式,也可以作为对象结构型模式。
使用前提或场景:解决两个已有接口间不兼容问题。client面向接口编程,而该面向的接口又与第三方接口不兼容,两者又不便于修改,则可用adapter协调工作
个人理解:不能说是"转换",而是协调不兼容接口间工作。比如客户端期望调用的方法传参是一个样子(面向接口编程,该方法可上升为目标抽象类,下述角色中有提到),而现有第三方实现的满足业务功能的接口可用,但规定的参数格式不一样。但两者(目标抽象类与第三方接口)都不愿或者不能修改,因此,则需要一个适配器来协调两者工作。
1.2 模式结构
包含如下角色:
target(目标抽象类)
客户期望的业务接口,可以是具体类,也可以是接口。adapter(适配器类)
适配器类可调用adaptee接口,以对 adaptee 和 target 进行适配,使其协调工作。adaptee(适配者类)
被适配的角色,定义了可工作、已存在、待适配的接口,些情况下甚至没有源代码。client(客户类)
客户类面对目标抽象类进行编程
可细分为两种模式:
- 类适配器模式:适配器类与适配者类是继承关系,因为java不支持多重继承,因此该模式下目标抽象类只能是接口。
- 对象适配器:适配器类与适配者类是关联关系(也可以称为委派关系),即含有适配者类的成员变量
1.3 模式扩展
1) 缺省适配器模式(default adapter pattern):当不需要实现接口提供的全部方法时,可先设计一个抽象类(缺省适配器)来实现该接口,并为每个方法提供一个默认实现(通常是空实现,也称钩子方法[hook method]),那么该抽象类的子类(具体业务类)可有选择的只覆盖父类中某些方法来实现需求。
interface serviceinterface{ void m1(); void m2(); void m3(); } abstract class abstractserviceclass implements serviceinterface{ public void m1(){} public void m2(){} public void m3(){} } class concreteserviceclass extends abstractserviceclass{ public void m2(){ system.out.println("具体业务方法"); } }
2) 双向适配器:适配器中同时包含对目标类和适配者类的引用。
1.4 优缺点
也是其使用场景,可以在不修改客户、适配者、目标抽象类前提下,让几者兼容工作。同时适配器也能很方便的替换,符合开闭原则。
2. 桥接模式(bridge pattern)
2.1 定义
"decouple an abstraction from its implementation so that the two can vary independently."
将抽象部分与它的实现部分解耦,使它们都能独立地变化
又称柄体(handle and body)模式,接口(interface)模式,属于对象结构型模式。
使用场景:当一个类存在两个独立变化的维度时,为了减少因继承结构带来的具体类数量,可将变化的维度进行抽象化,再用关联方式将其联系起来。
个人理解:如上面所讲,多变化维度如果用继承将会大大增加类的数量。比如下面例子中的"跨平台多格式播放器",若将其中一个维度用继承关系表达,再在其中定义另一个维度接口的成员变量,以此达到灵活组合的目的。
2.2 模式结构
abstraction(抽象类)
其中一个变化维度的继承关系结构中的抽象父类,一般来说 implementor 接口仅提供基本操作,而 abstraction 则可能会做更多更复杂的操作。其中定义了一个 implementor 类型对象,并维护该对象,达成关联关系.refinedabstraction(扩充抽象类)
implementor(实现类接口)
另一变化维度所抽象出来的接口concreteimplementor(具体实现类)
2.3 优点
通过将另一维度抽象,并使用关联关系,一定程度上进行了解耦,也 满足了"合成复用原则",大大减少了因静态抽象继承结构可能带来的类的数量。
因为抽象,客户端面向两个维度抽象层编程,加上新增扩充抽象类或具体实现类均不需要修改其他任何代码,因此很好的符合了"依赖倒转原则"与"开闭原则"。
3. 组合模式(composite pattern)
3.1 定义
"compose objects into tree structures to represent part-whole hierarchies. composite lets clients treat individual objects and compositions of objects uniformly."
组合多个对象形成树形结构以表示"部分——整体"的层次结构。组合模式使客户能统一对待单个对象(即叶子对象)和组合对象(即容器对象)
又称"部分-整体"(part-whole)模式,属于对象的结构模式
使用场景:在具有整体(容器)和部分(叶子)的类层次结构中,希望能忽略两者的差异,使客户能一致对待它们。
个人理解:通过定义一个抽象构件类,既能代表叶子也能代表容器构件,其中声明了构件的统一业务方法,该方法叶子与容器有不同的实现,但客户面向该抽象构件编程,因此可以统一处理而无须关心两者间差异。
3.2 模式结构
component(抽象构件)
可以是接口或者抽象类,声明叶子构件与容器构件共有业务方法leaf(叶子构件)
composite(容器构件)
在容器构件中,包含了一个集合用于储存子结点,该子结点可以是叶子、也可以是容器对象。且额外提供了管理子结点的方法,如addleaf(component com)等等client(客户类)
注:该模式又可细分为:透明组合模式、安全组合模式
透明组合模式:即将容器类中的额外方法(如addleaf),提到抽象构件类中,使得所有构建类都有相同的接口,客户可透明的完全一致地对待所有对象。
缺点很大:因为叶子对象和容器对象本质上是有区别,叶子对象不可能有成员对象。因此add等方法对叶子类是没有意义的,在运行时调用会出错。安全组合模式(推荐):即没有在抽象构件类中声明本该容器具有的方法,这是安全的。但这会造成客户不能完全针对抽象编程(因为add等方法是定义在容器类中的,只有声明为容器类型才能调用)无法一致使用叶子与容器构件。
4. 装饰模式(decorator pattern)
4.1 定义
"attach additional responsibilities to an object dynamically. decorators provide a flexible alternative to subclassing for extending functionality."
动态地给一个对象添加职责。相较于继承,装饰模式则提供了一种更为灵活的方式来扩展功能。
又称"油漆工模式"、"包装器(wrapper)"(与适配器模式别名相同,但含义不同),是对象结构型模式。
使用场景:无须使用继承,通过关联机制来动态、透明的为对象增加职责。
个人理解:定义抽象构件(类或接口)声明业务方法,具体构件与装饰类均实现该接口。在装饰类中定义了构件类型的成员变量,在接口方法中调用该对象的方法并添加额外实现。而对客户来说是一直的,因为其面向抽象编程。
该模式也能达到循环装饰,来添加功能。
4.2 模式结构
component(抽象构件)
声明了业务方法,客户端面向该抽象编程concretecomponent(具体构件)
实现了抽象构件decorator(抽象装饰类)
(可选)实现抽象构件。当有多个装饰类时,可声明抽象装饰类,用以定义所有装饰类的统一行为,如维护一个抽象构件类型的引用。假如只有一个装饰类时,则可省略concretedecorator(具体装饰类)
内部调用被装饰对象方法,并额外增添新的功能。
注:该模式可细分为透明装饰模式与半透明装饰模式。
- 透明装饰模式:即装饰类中未额外添加public方法,客户端可完全一致的使用具体构建类与装饰类。
- 半透明装饰模式:装饰类中增添了自己的方法,意味着客户端必须声明为装饰类型才可调用,因此无法一致对待。
4.3 优点
一定程度上降低了静态继承带来的耦合度,符合”合成复用原则“,同时抽象层的定义也符合”开闭原则“,比如新增具体装饰类无须修改其他代码。
5. 外观模式(facade pattern)
5.1 定义
"provide a unified interface to a set of interfaces in a subsystem. facade defines a higher-level interface that makes the subsystem easier to use."
为子系统的一组接口提供一个统一的入口。外观模式定义了一个高层接口使得能更方便地使用子系统。
又称门面模式,是对象结构型模式
使用场景:若想简化客户端与复杂子系统间的操作,则可引入外观角色来为复杂子系统提供简化的入口
个人理解:如上所述,客户端只需与外观角色交互,而由外观角色来整合调用子系统中的复杂接口
5.2 模式结构
subsystem(子系统角色)
每个子系统都可由客户端直接调用,也可以被外观角色调用。子系统并不知道外观角色的存在,对子系统而言,外观角色仅仅是另一个客户端而已facade(外观角色)
它将从客户端发来的请求委派到相应子系统
5.3 优点
如同使用情景一样,使用外观模式可为客户端提供很大方便,将客户端与子系统的内部复杂性分隔开,只需与外观角色交互即可。降低了客户与子系统耦合度,符合"迪米特法则"。
6.享元模式
todo
7. 代理模式(proxy pattern / surrogate pattern)
7.1 定义
"provide a surrogate or placeholder for another object to control access to it."
为某一对象提供一个代理,由代理对象来控制对原对象的引用
属于对象结构型模式
使用场景及个人理解:为现有对象的使用提供额外的控制,而无须修改现有类与客户调用,对客户端而言是透明的,无须关心具体实现,只需面向接口使用即可。java中官方自带有对动态代理的支持。
和“装饰模式”的不同:(1)代理中的引用是对真实主题角色的引用,而装饰模式中是对抽象主题角色的引用。(2)装饰模式的被装饰对象是由客户传入的,想额外增加修饰功能。而代理模式是内部创建被代理对象,且完全控制其行为。
7.2 模式结构
subject(抽象主题角色)
定义了业务方法,是真实主题与代理主题的共同接口proxy(代理主题角色)
内部包含对真实主题角色的引用,可完全控制真实主题的使用逻辑realsubject(真实主题角色)
7.3 扩展
常见应用:
- 远程代理:远程代理可以将网络细节隐藏起来,使客户端不必考虑网络的存在。比如java的rmi(remote method invocation,远程方法调用),客户对象在客户端运行,想服务器端运行的远程对象发起请求。
- 虚拟代理:一种时间换空间的内存节省技术,将占用大量内存或处理复杂的对象推迟到使用它时才创建。比如hibernate的懒加载
java中的动态代理,以及优缺点:
todo:一个链接