《设计模式》-04-GoF模式-结构型模式(适配器模式,桥模式,组合模式,装饰器模式,门面模式,享元模式,代理模式)
文章目录
1 适配器模式(Adapter)
指将某种接口或数据结构转换为客户端期望的类型,使得不兼容的类或对象能够一起协作。
适配器解决的主要问题是,目标类(或对象)所提供的服务不是客户端所期望的类型,但修改客户端或目标类的代价很大。
1.1 使用场景
- 想要使用已存在的目标类(或对象),但它没有提供客户端所需要的接口类型,而更改目标类(或对象)或客户端已有代码的代价都很大。
如:在软件维护阶段,软件产品的代码已经开发完成,当有新的功能需求需要实现的时候,必须在已有代码的基础上添加新的代码,以实现新功能需求。
如果新需求的代码已经实现,却因为接口不兼容等原因无法和已有代码协作,就出现了该场景所描述的问题。
对于新需求或已有软件,假设任何一方的代码变更代价都很大,则需要一种更小代价的方式使它们能够兼容协作,适配器模式就是一种解决该类问题的可行方案。
- 想要复用某个类,但使用该类的客户类信息是预先无法知道的。
1.2 类结构
- 类适配器
类适配器通过继承被适配类的方式调用目标服务
- 适配器类Adapter实现Client所期望的接口Target,并继承被适配类Adaptee。
- Client期望使用的服务为service(),Adaptee提供的服务为anotherService()
- anotherService()通过Adapter转换为service()。
因此Adaptee不需要改变自己的服务,Client也不需要改变自己的需求,通过添加Adapter类的方式,即可以较小代价使Client与Adaptee能够一起协作。
- 对象适配器
通过被适配对象引用调用目标服务,
适配器类Adapter实现客户端(Client)所期望的接口Target,通过对象引用的方式使用被适配类Adaptee提供的anotherService()服务
2 桥模式 (Bridge)
使用组合关系将代码的实现层和抽象层分离,让实现层与抽象层代码可以分别自由变化。
2.1 使用场景
(1)抽象层代码和实现层代码分别需要自由扩展。
比如,视图渲染的实现层代码需要面向不同的操作系统平台,当客户端使用视图渲染服务时,如果耦合到平台实现层代码,就需要面向不同的平台开发多个客户端程序,致使软件开发成本成倍增加;如果将抽象层代码与实现层代码分离,客户端只依赖抽象层,那么在面向不同平台扩展实现层代码时就不会影响到客户端,同样地,抽象层代码的变化也不会影响到实现层,二者可以自由扩展。
(2)需要减弱或消除抽象层与实现层之间的静态绑定约束。
(3)需要向客户端完全隐藏实现层代码。
如于保护实现层代码的目的
(4)需要独立封装或复用实现层代码。
2.2 类结构
结构分成3个部分,分别为客户端Client、抽象层Abstraction和实现层Implementor。其中,抽象层Abstraction将Client与Implementor分离
3 组合模式(Composite)
指使用组合和继承关系将聚合体及其组成元素分解成树状结构,以便客户端在不需要区分聚合体或组成元素类型的情况下使用统一的接口操作它们。
诠释:组合模式遍历聚合体。即使聚合体组成元素变化,一样可以便利,而不必修改客户端。
3.1 使用场景
(1)便于聚合体加入新元素。
当聚合体进行资源管理时,使用树的遍历方式能够方便地实现对子节点的操作。由于软件需求不稳定性,常常需要对聚合元素的子类进行扩展,比如,前端视图子系统在新迭代阶段通常会扩展出新的组件类型,而新的视图组件仍然需要聚合在视图容器中,以便使用或管理。
(2)简化客户端代码结构
客户端在操作聚合体或聚合元素时,不因聚合体或聚合元素类型不同而改变操作过程(或算法)。这时,可以将聚合体和聚合元素抽象成相同的对象类型,从而简化客户端的代码结构。
3.2 类结构
- Composite是聚合体类型,Leaf是聚合元素叶节点类型,它们共同抽象为统一的Component接口向客户端Client提供service()服务。
- 请注意,Composite聚合体类型需要管理聚合元素,因此定义了add()、remove()等聚合元素管理行为,聚合元素管理的数据结构可以是List或其他集合类型。
- 当客户端Client操作组合元素Leaf或聚合体Composite时,并不用区分它们的具体类型,只作为Component类型即可。Component类
4. 装饰器模式(Decorator)
通过包装(不是继承)的方式向目标对象中动态地添加或删除功能。
4.1 使用场景
(1)动态地向目标对象添加功能,而不影响到其他同类型的对象。
(2)对目标对象进行功能扩展,且能在需要时删除扩展的功能。
(3)需要扩展目标类的功能,但不知道目标类的具体定义,无法定义其子类。
(4)数量巨大的子类只在扩展行为上有区别,需要减少类的数量。
4.2 类结构
- Component接口定义了目标类型的基本行为service()(所有Component类型的对象都具有的行为),
- 由子类ConcreteComponent实现
- Decorator是对目标类型进行包装的装饰器抽象类,会将客户端请求转发给被包装对象。
- ConcreteDecorator继承Decorator抽象类,实现被包装类新功能的扩展。
- ConcreteDecorator对象可以用新服务newService()+service()的方式向客户端提供组合服务,也可以提供单独的服务。
5. 门面模式(Facade)
向客户端提供使用子系统的统一接口,用于简化客户端使用子系统的操作。
5.1 使用场景
- 客户端与子系统强耦合的缺点
(1)当子系统升级时,客户端将会受到影响,甚至会迫使客户端不得不升级。
(2)由于客户端使用了不同的子系统服务类,增加了客户端使用子系统服务的复杂程度。
(3)子系统实现细节被暴露给客户端,会给子系统带来安全隐患等问题。 - 门面模式使用场景
(1)想要简化客户端使用子系统的接口。
(2)需要将客户端与子系统进行独立分层。
(3)向客户端隐藏子系统的内部实现,用于隔离或保护子系统。 - 示例
下图分别是 非门面模式和门面模式
5.2 类结构
6. 享元模式(Flyweight)
采用共享方式向客户端提供数量庞大的细粒度对象。
所谓细粒度对象,是指实现了业务细节并相互独立的对象。
细粒度对象是一种相对概念,一般不会进行更小粒度的拆分。
6.1 使用场景
(1)运行时产生大量的相似对象,而这些对象的状态被客户端管理,开发人员想要减少运行时对象生成的数量。
(2)向客户端提供对象的共享实例,以提高程序的效率。
6.2 类结构
- Flyweight是享元接口,定义了享元对象提供给客户端的服务service(),客户端Client使用享元对象的服务时,设置享元对象的外部状态;
- ConcreteFlyweight是具体享元类,实现享元接口,向客户端提供service()服务的实现;
- ConConcreteFlyweight对象状态分为内部状态instrinsicState和外部状态extrinsicState,instrinsicState共享给所有客户端,extrinsicState由客户端持有。
6.3 示例
COS系统会在节日时向所有客户发送关怀信息,预计客户规模在10万以上。根据不同的客户等级(普通客户、VIP客户、SVIP客户),关怀信息问候语的称谓及信息头部样式会有所不同,但信息的尾注及COS的Logo是一样的。COS消息发送子系统(或进程)负责发送各类信息。
7. 代理模式 (Proxy)
是用于控制客户端访问目标对象的占位对象
7.1 使用场景
(1)向客户端提供远程对象的本地表示。
(2)向客户端提供按需使用的昂贵对象,如写时复制(由于目标对象是昂贵的,只在其状态被改变时进行复制)。
对象昂贵指该对象占用较多的程序资源,或对象实例化是耗时操作,或其他影响程序运行效率的情况。
由于昂贵对象会对程序运行效率带来较大影响,在没真正调用昂贵对象的服务前,可以向客户端提供代价较小的同类型对象引用,作为保障客户端程序正常运行的条件。以较小代价代替目标昂贵对象,保障客户端程序正常运行的代理称为虚拟代理。
(3)控制(过滤)客户端对目标对象的访问。
(4)客户端访问目标对象服务时,需要执行额外的操作。
客户端访问目标对象服务时,需要执行额外的操作,这些额外的操作既不是客户端的行为,也不是目标对象的行为,因此,需要将这些额外的操作定义到一个单独的类型中,当客户端提交目标对象请求时,在目标对象处理该请求之前或之后执行那些被额外定义的操作。定义了额外操作的代理称为智能引用(Smart Reference)。
7.2 类结构
- Subject接口定义了目标类向客户端提供的所有服务方法;
- Proxy和RealSubject分别实现Subject接口;
- RealSubject实现了真正的客户服务。
在程序运行时,客户端Client持有的Subject对象引用指向Proxy实例,因此
- 客户端对象提交的请求由Proxy对象接收
- Proxy对象将该请求转交给RealSubject对象处理。