设计模式-结构型模式
一级目录
桥接模式
桥接模式主要通过分离接口与实现,解决类的接口与实现两个维度的变化。
若为单维度变化:使用组合或者继承均可。
1.分离接口与实现:
接口:
class A {
public:
virutal ~A( );
virtual void f( ) ;
virtual void g( );
};
实现:
class ImpA {
public:
virtual ~ImpA();
virtual void f( );
virtual void g( );
protected:
int x;
int y;
};
2.连接接口与实现:
其实就是在接口中组合实现。
class A {
public:
virutal ~A(ImpA * p):impA(p) {}
virtual void f( ) { impA->f( ); }
virtual void g( ) { impA->g( );}
private:
ImpA * impA;
};
class ImpA {
public:
virtual ~ImpA();
virtual void f( );
virtual void g( );
protected:
int x;
int y;
};
桥接模式的好处:
- 分离了接口及其实现
- 使得各部分可独立变化、扩展、
- 可对客户隐藏实现部分
- 也称Handle模式/Handle-Body模式
适配器模式
适配器模式主要是对已有且被大量使用的旧接口进行适配,得到一个新接口完成目标任务。主要用于软件开发后期,旧的软件接口已被大量使用,但是新的软件接口功能相同但是接口有差异,无法直接使用,这时如果重新统一接口设计,需要消耗大量的资源(时间、人力、物力),简单的使用一个适配器作为连接新旧接口的中转站就成为首选。
解决方式:
-
类的适配器方式(多重继承)
-
对象的适配器方式(利用组合)
装饰模式
装饰模式:是动态的对类的对象增添一些可能执行或者不执行的行为,本质仍是通过组合方式实现的,而且可以通过多个装饰类层层叠加执行多个不同的行为。通常是继承基类如Animal,拥有一个基类对象的数据成员如animal,override成员函数如Eat时,增添额外的行为,并执行animal本身常规的行为,进而实现行为职责的扩充。
装饰模式结构如下:
举例结构如下:
拓展行为(层层叠加new)
优点:
- 保持抽象层上关系的稳定;
- 比单独使用组合或继承灵活;
- 随时扩展功能或职责;
不足: - 可能产生许多更小的子类
- Component类过大时,效率较低,此时可考虑使用策略模式
合成模式
又称组合模式,起初的动机为组合成树形结构。
定义:将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
核心:借助同一接口,使叶子节点和树枝节点的操作具备一致性。
本质:统一叶子对象和组合对象(树枝对象)。
合成模式常用来表示部分-整体的关系,组合后的
好处:可用树形结构表示线性的整体-部分关系;
访问任意一个结点时,由于子类聚合父类,不需要判定或明确结点类型
树形结构一般化表示如下:
- 树枝节点:相当于容器,储存叶子结点和树枝节点。
无论是叶结点还是树枝节点都继承同一种类型
,使得用户无需辨别树中结点是叶子结点还是树枝节点而可以直接进行操作,进而给用户带来极大的便利。
结构
- 抽象根节点(Component):定义系统各层次对象的共有方法和属性。它的主要作用是为树枝节点和树叶节点声明
公共接口
,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树节点完成。 - 树叶节点(Leaf):是组合中的叶子节点对象,其下再无子节点,用于实现抽象根节点中 声明的公共接口,它是系统层次遍历的最小单位。
- 树枝节点(Composite):是组合中的分支节点对象,它有子节点。它实现了抽象根节点中声明的接口,它的主要作用是存储和管理子节点,组合树枝节点和叶子节点形成一个树形结构,通常包含
AddChild(),RemoveChild(),GetChild(int),GetChildren()
等方法。
组合模式的两种实现模式
透明模式
透明组合模式把组合(树枝节点、树叶节点)使用的方法全部放到抽象根节点
(Component)中,让不同层次的节点(树枝节点、树叶节点)的结构都具备相同的行为。
在透明组合模式中,由于抽象根节点声明了所有子类中的全部方法,所以产生的好处是客户端无须分辨是树叶节点(Leaf)和树枝节点(Composite),它们具备完全一致的接口,对客户端来说是透明的;但其缺点是树叶节点会继承得到一些它所不需要的方法(管理子类操作的方法,如树叶节点本来没有 Add()、Remove() 及 GetChild() 方法,但是由于被继承了下来,因此需要进行实现,实现为空方法或者抛异常),这与设计模式中接口隔离原则相违背。
安全模式
抽象根节点(Component)只规定各个层次的最基础的一致行为,而把组合(树枝节点)本身的方法(如管理子类对象的添加、删除等)放到自身当中。
在该方式中由于将树枝节点特有的行为放到自身当中,树叶节点没有对子对象的管理方法,带来的好处是接口定义职责清晰,符合设计模式的接口隔离原则和单一职责原则,避免了上一种方式的安全性问题;
但是由于树枝节点和树叶节点有不同的接口,使得客户端调用时需要区分树枝节点(Composite)和树叶节点(Leaf),这样才能正确处理各个层次的操作,客户端无法依赖抽象(Component),失去了透明性,违背了设计模式的依赖倒置原则。
举例
适用情况![在这里插入图片描述](https://img-blog.csdnimg.cn/c8dd66ba1ea54aa0b3fd6afd0c68cde7.png)
门面模式
也称外观模式。
定义:将复杂的子系统封装得到一个统一的接口(Facade),供客户端调用,实现子系统和客户端接口隔离。
结构如下:
好处:
- 对用户隐藏了系统的实现,易用;
- 实现子系统和客户端接口隔离,降低了Client与子系统之间的耦合,利于复杂系统功能模块统一管理和编程。
- 客户端
仍然可以越过门户直接与子系统内部交互
;
适用性
- 为复杂系统提供简单接口;
- 为多个子系统提供统一的“门面”,保持子系统的独立变化性;
- 便于构建层次化的系统;
代理模式
在A对象访问B对象时,为B对象提供一个中介(代理)Proxy,使得A对象不再直接访问B对象,而是直接访问Proxy,由Proxy自行决定A是否与B对象交互
。这样就可以控制对B对象的访问。
代理模式结构图
代理类型
- 远程代理(Remote Proxy):隐藏目标对象的位置信息
- 虚拟代理(Virtual Proxy):
隐藏具体的访问过程及具体实现细节
- 保护代理(Protection Proxy):保证访问的安全性
- 灵巧(智能)指针(Smart Pointer)
例子
享元模式
轻量级-Flyweight,享元模式通过共享技术实现相同或相似对象的重用
,支持对大量细粒度对象的复用。
享元模式以共享的方式高效地支持大量细颗粒对象,共享的关键是区分内部状态和外部状态:
- 内部状态:内部状态可被共享,是存储在享元对象内部且
不会随环境状态改变而改变的状态
。 - 外部状态:
外部状态不可被共享,实际上随环境改变而改变的
。享元对象的外部状态必须由客户端保存,并在享元对象被创建后,在需要使用的时候再传入享元对象内部。
简单来说,一个具有内部状态共享的对象,通过赋予不同外部状态达到得到不同的对象的目的。
结构图
(1)Flyweight(抽象享元类)
描述一个接口,通过这个接口flyweight可以接受并作用于外部状态。抽象享元类中定义了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法设置外部数据(外部状态)。
(2)ConcreteFlyweight(具体享元类)
具体享元类实现了抽象享元接口,其实例称为享元对象。具体享元对象必须是可以共享的。
(3)UnsharedConcreteFlyweight(非共享具体享元类)
并不是所有的抽象享元类的子类都需要共享,不能被共享的子类则设计为非共享具体享元类,当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
(4)FlyweightFactory(享元工厂类)
创建并管理享元对象。它需要确保合理地共享享元对象;当用户请求一个具体享元对象时,享元工厂对象提供一个已创建的实例,如果请求的实例不存在的情况下,就新创建一个实例。
优缺点
享元模式优点:
(1)极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份;
(2)享元对象的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
享元模式缺点:
(1)享元模式使得系统更加复杂,需要分离出内部状态和外部状态,从而使得程序的逻辑复杂化。
(2)为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
应用场景
(1)当一个系统有大量相同或相似的对象,由于这些对象的大量使用,造成内存的大量耗费;使用享元模式可以节约内存空间,提高系统的性能。
(2)对象的大部分状态都可以外部化
,可以将这些外部状态传入对象中;
如:一篇文章中对于字母的使用(字母本身abcd为内部状态,字体大小等为外部状态);一个棋盘中棋子(黑棋、白棋为内部状态,位置为外部状态)