2(本文主要参考了《设计模式》、李建忠设计模式视频、《大话设计模式》)
目录
4.8.2 Composite、Decorator与Proxy
5.1 职责链模式(Chain of Responsibility)
1 概述
将稳定部分和变化部分进行分离,稳定部分封装到一块,变化部分通过传参来实现(变化部分通过一个基类,具体的情况继承这个基类)。变化是复用的天敌!面向对象设计最大的优势在于:降低变化,提高复用。
对象是什么?从语言层面来看,对象封装了代码和数据。从规格层面来看,对象是一系列可被使用的公共接口。从概念层面来看,对象是某种拥有责任的抽象。
任何一种设计模式,可以用一个结构图来表示,其能适应变化的部分则为其优点(易于拓展),其稳定部分(不利于拓展的地方)即为其缺点。
设计模式根据目的可以划分为创建型(Creational)、结构型(Structural)和行为型(Behavioral)三种。创建型模式与对象的创建有关,结构型模式处理类或对象的组合,行为型模式对类和对象怎样交互和怎样分配职责进行描述。
设计模式根据范围可以分为类模式和对象模式,类模式处理类和子类之间的关系,这种关系通过继承来建立,是静态的,编译时刻就已经确定下来了。对象模式处理对象间的关系,这些关系在运行时是可以变化的,更加具有动态性。从某种意义上,几乎所有的模式都使用了继承机制,所以“类模式”只指那些集中于处理类间关系的模式,而大部分模式都属于对象模式的范畴。
类创建型模式将对象的部分创建工作延迟到子类,而对象创建型模式则将它延迟到另一个对象中。类结构型模式使用继承的方式来组合类,而对象结构型模式则以对象组装的方式。类行为型模式使用继承的方式来描述算法和控制流,而对象行为型模式则描述一组对象怎样协作完成单个对象所无法完成的任务。
2 面向对象的设计原则
2.1 依赖倒置原则(DIP)
高层模块(稳定)不应该依赖低层模块(变化),二者都应该依赖于抽象(稳定)。
抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)。
2.2 开闭原则(OCP)
对拓展开发,对更改封闭。
类模块应该是可以拓展的,但是不可以修改的。
2.3 单一职责原则(SRP)
一个类应该仅有一个引起它变化的原因。
变化的方向隐含着类的责任。
2.4 Liskov替换原则(LSP)
子类必须能够替换它们的基类(Is a)。
继承表达类型抽象。
2.5 接口隔离原则(ISP)
不应该强迫客户程序依赖它们不用的方法。
接口应该小而完备。
2.6 优先使用对象组合,而不是继承
类继承通常为“白箱复用”,对象组合通常为“黑箱复用”。
继承从某种程度下破坏了封装性,子类父类耦合度高。
而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。
2.7 封装变化点
使用封装来创建对象之间的分解层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。
2.8 针对接口编程,而不是针对实现编程
不将变量声明为某个特定的具体类,而是声明为某个接口。
客户程序无需获知对象的具体类型,只需知道对象所具有的接口。
减少系统各部分的依赖关系,从而实现“高内聚、低耦合”的类型设计方案。
3 创建型模式
创建型模式抽象化了实例化过程,帮助一个系统独立于如何创建、组合和表示它的那些对象。类创建型模式使用继承改变被实例化的类,对象创建模式将实例化委托给另一个对象。两个主旋律:将系统使用哪些具体的类的信息都封装起来,隐藏了这些类的实例是如何创建和放在一起的。
主要包括抽象工厂(Abstract Factory)、建造者模式(Builder)、工厂方法(Factory Method)、原型模式(Prototype)、单例模式(Singleton)。
对象创建型模式,将实例化委托给另一个对象,绕开new,从而避免对象创建(new)过程所导致的紧耦合(依赖具体的类),从而支持对象创建的稳定。
3.1 抽象工厂(Abstract Factory)
属于对象创建型模式,提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
实用性:
- 一个系统要独立于它的产品创建、组合和表示时;
- 一个系统要由多个产品系列中的一个来配置时;
- 当你需要强调一系列相关的产品对象的设计以便进行联合使用时;
- 当你提供一个产品类库,而只想显示他们的接口而不是实现时。
结构图:
(红色稳定,绿色和蓝色变化)
优缺点:
- 分离了具体的类。一个工厂封装了创建产品对象的责任和过程,它将客户与类的实现进行了分离。
- 使得易于交换产品系列。只需改变具体的工厂即可使用不同的产品配置,一个抽象的工厂创建了一个完整的产品系列(工厂是可以拓展的)。
- 有利于产品的一致性性。当一个系列中的产品对象被设计成一起工作时,一个应用一次只能使用同一个系列中的对象。
- 难以支持新的产品种类。结构图中只支持ProductA和ProductB两种产品,当有ProductC时,需要更改大量的代码。改模式适应于产品种类稳定(种类不会发生变化)。
代码:
// 抽象工厂模式
#pragma
#include <iostream>
using std::cout;
using std::endl;
//产品类接口,产品的种类必须固定,否则不适合该模式
//产品,CPU接口
class ICpu
{
public:
ICpu() {}
virtual void funcCpu() = 0;
};
//产品,RAM接口
class IRam
{
public:
IRam() {}
virtual void funcRam() = 0;
};
//抽象工厂类,配件工厂
class IComponentFactory
{
public:
IComponentFactory() {}
virtual ~IComponentFactory() {}
virtual ICpu* CreateCpu() = 0;
virtual IRam* CreateRam() = 0;
};
//一个具体的工厂及其相关的产品
//具体产品,IntelCpu
class IntelCpu : public ICpu {
public:
IntelCpu() {}
void funcCpu() override {
cout << "我是 IntelCpu !!!" << endl;
}
};
//具体产品,IntelRam
class IntelRam : public IRam {
public:
IntelRam() {}
void funcRam() override {
cout << "我是 IntelRam !!!" << endl;
}
};
//一个具体的工厂
class IntelFactory : public IComponentFactory
{
public:
IntelFactory(){}
ICpu* CreateCpu() override {
return new IntelCpu();
}
IRam* CreateRam() override {
return new IntelRam();
}
};
//一个具体的工厂及其相关的产品
//具体产品,AmdCpu
class AmdCpu : public ICpu {
public:
AmdCpu() {}
void funcCpu() override {
cout << "我是 AmdCpu !!!" << endl;
}
};
//具体产品,AmdRam
class AmdRam :public IRam {
public:
AmdRam() {}
void funcRam() override {
cout << "我是 AmdRam !!!" << endl;
}
};
//一个具体的工厂,Amd工厂
class AmdFactory : public IComponentFactory {
public:
AmdFactory() {}
ICpu* CreateCpu() override {
return new AmdCpu();
}
IRam* CreateRam() override {
return new AmdRam();
}
};
//可以创建新的工厂,比如英伟达工厂等
//当主函数include该头文件时,该函数会被编译两遍,发生重定义,这点要注意
void Test_AbstractFactory()
{
IComponentFactory* pFactory = new IntelFactory();
//IComponentFactory* pFactory = new AmdFactory();
ICpu* pCpu = pFactory->CreateCpu();
pCpu->funcCpu();
IRam* pRam = pFactory->CreateRam();
pRam->funcRam();
}
3.2 建造者模式(Builder)
建造者模式(Builder),将一个复杂的对象的构建与它的表示相分离,使得同样的构建过程可以创建不同的表示。
如果使用了建造者模式,用户指定建造的类型,而具体的建造过程就不需要知道。
- 稳定部分,分步骤构建一个复杂的对象,构建的过程步骤是稳定的,第一步做什么,第二步做什么等(比如,画小人,第一步画头,第二步画身体,第三步画胳膊等),可以用一个指挥者(操作者)类,建造者的参数是一个小人,小人的高矮胖瘦用这个小人进行传参,而小人先画什么,再画什么,已经固定。
- 变化部分,每个步骤里面的参数是变化的,比如第一步画头(头的大小是变化的,需要传参),第二步画身体(身体大小),第三步画胳膊等。将小人作为一个基类,画一个胖子还是瘦子继承这个小人。
- 不足,当算法步骤不固定时,不宜采用此方法。
(上图图源来自李建忠老师教学视频,下面图同)。
代码:
#include <iostream>
using std::cout;
using std::endl;
class xiaoren { //builder
public:
virtual void settou(int t) = 0;
virtual void setgebo(int t) = 0;
virtual void settui(int t) = 0;
virtual char* getname() = 0;
};
class pangzi : public xiaoren {
public:
void settou(int t) override{
//其他一大堆复杂操作
cout << "胖子的头:" << t << endl;
}
void setgebo(int t) override {
//其他一大堆复杂操作
cout << "胖子的胳膊:" << t << endl;
}
void settui(int t) override {
//其他一大堆复杂操作
cout << "胖子的腿:" << t << endl;
}
char* getname() override {
return "胖子";
}
};
class huajia { //控制者
public:
huajia(xiaoren* _xr) {
xr = _xr;
}
xiaoren* xr;
void huaxiaoren() { //画法固定
cout << "开始画" << xr->getname()<<endl;
cout << "第一步画头" << endl;
cout << "第二步画胳膊" << endl;
cout << "第三步画腿" << endl;
}
};
void Test_Builder() {
xiaoren* xr = new pangzi;
xr->settou(2); //不同的表示
xr->setgebo(4);
xr->settui(6);
huajia hj(xr);
hj.huaxiaoren();
}
3.3 工厂方法(Factory Method)
属于对象创建型模式,定义一个用于创建对象的接口,让子类决定实例化哪一类。工厂方法使一个类的实例化延迟到了子类。
在软件系统中,经常面临对象创建的工作;由于需求的变化,需要创建的对象的具体类型经常变化。如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?
实用性:
- 当一个类不知道它所必须创建的对象的类的时候。
- 当一个类希望由它的子类来指定它所创建的对象的时候。
- 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
结构图:
优缺点(特点):
- 工厂方法模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件变脆弱。
- 工厂方法通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种拓展(而非更改)的策略,较好地解决了这种耦合关系。
- 工厂方法模式解决“单个对象”的需求变化。缺点在于要求创建方法(函数)/参数相同。
- 类比抽象工厂,抽象工厂是多个产品(而且产品的种类是固定的),抽象工厂的拓展方向是工厂。而工厂方法是单个产品,拓展的方向是产品。
代码:
//工厂方法模式
//定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使得一个类的实例化延迟到子类。
//目的:解耦,手段:虚函数
#pragma
#include <iostream>
using std::cout;
using std::endl;
//产品类,可能有多个产品,产品和工厂之间是一对一的关系
//抽象产品接口
class IComputer
{
public:
IComputer() {}
virtual ~IComputer() {}
virtual void funcWork() = 0;
};
//抽象工厂类
class IFactory
{
public:
IFactory() {}
virtual ~IFactory() {}
virtual IComputer* createComputer() = 0; //FactoryMethod()
};
//具体的产品和具体的工厂
class AmdComputer : public IComputer {
public:
AmdComputer() {}
void funcWork() override {
cout << "AmdComputer Work!!!" << endl;
}
};
class AmdFactory : public IFactory {
public:
AmdFactory() {}
IComputer* createComputer() override {
return new AmdComputer();
}
};
class IntelComputer : public IComputer {
public:
IntelComputer() {}
void funcWork() override {
cout << "IntelComputer Work!!!" << endl;
}
};
class IntelFactory : public IFactory {
public:
IntelFactory() {}
IComputer* createComputer() override {
return new IntelComputer;
}
};
void Test_FactoryMethod() {
IFactory* pFactory = new IntelFactory();
//IFactory* pFactory = new AmdFactory();
IComputer* pComputer = pFactory->createComputer();
pComputer->funcWork();
}
3.4 原型模式(Prototype)
属于对象创建型模式,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建的对象。实际就是实现一个拷贝(一般为深拷贝)构造函数,实现一个.clone()函数,里面调用拷贝构造函数。
在软件系统中,经常面临着“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但它们却拥有比较稳定一致的接口。
结构图:
3.5 单例模式(Singleton)
属于对象创建型模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。
适用性:
当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
当这个唯一实例应该是通过子类化可拓展的,并且客户应该无需更改代码就能使用一个拓展的实例时。
结构图:
代码:
//单例模式
class Singleton
{
private:
Singleton(){ //防止构造对象
static delCST dc;
}
Singleton(const Singleton& otherobj) { };
//以上可以防止在栈上产生对象
//以下两种防止在堆上产生对象,不可以进行new操作
//static void* operator new (size_t);
//static void* operator new[](size_t);
//一个内嵌的类,仅提供析构单例的作用
class delCST {
public:
delCST(){} //构造函数
~delCST() {
if (m_instance != nullptr)
delete m_instance;
}
};
public:
static Singleton* getInstance();
static Singleton* m_instance;
};
Singleton* Singleton::m_instance = nullptr;
//线程非安全版本,多线程不安全,单线程中是安全的
//实际只要保证第一次调用时单线程调用,以后再进行多线程调用该函数也是安全
Singleton* Singleton::getInstance() {
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
//线程安全版本,但锁的代价相对较高
Singleton* Singleton::getInstance() {
Lock lock; //实际应为一个成员变量加锁,这样写方式不对
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
//双检测锁,实际也不安全,因为new操作可能会乱序执行
Singleton* Singleton::getInstance() {
if (m_instance == nullptr) {
Lock lock;
if (m_instance == nullptr) {
m_instance = new Singleton();
}
}
return m_instance;
}
//C++ 11版本之后的跨平台实现 (volatile)
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;
Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_release);//释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
4 结构型模式
结构型模式涉及到如何用类和对象以获得更大的结构。主要分为类结构型模式和对象结构型模式两种,类结构型模式通常采用继承机制来组合接口或实现;对象结构型模式不是对接口和实现进行组合,而实描述了如何对一些对象进行组合,从而实现新功能的一些方法。对象结构型模式可以在运行时改变对象的组合关系,所有对象组合的方式具有更大的灵活性。
4.1 适配器(Adapter)
将一个类的接口转换成用户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新的环境要求的接口是对这些现存对象所不满足的。
将已存在的对象放在新的环境中,需要将新环境接口作为基类,然后派生出子类(适配器),将已存在的对象作为适配器的成员函数。
适配器继承新接口,适配器的成员变量是旧接口。
适用性:
- 使用一个已经存在的类,而它的接口不符合你的要求。
- 创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
- (仅适用于对象适配器)使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。
结构图:
类适配器:
- 采用多重继承适配器接口。类适配器用一个分支继承接口,用另一个分支继承接口的实现部分。在C++中,用共有方式继承接口(新接口),用私有方式继承接口的实现(旧接口)。
- 用一个具体的Adapter类对Adaptee(旧接口)和Target(客户调用的接口)进行适配,当我们想要匹配一个类以及所有它的子类时,类适配器将不能适合。
- 使用类适配器可以重定义Adaptee的部分行为,因为Adapter时Adaptee的一个子类。
- 仅仅引入了一个对象,并不需要额外的指针以间接得到Adaptee。
对象适配器:
- 允许一个Adapter与多个Adaptee,即适配器可以与Adaptee本身以及它的所有子类同时工作。Adapter也可以一次给所有的Adaptee添加功能。
- 使得重定义Adaptee比较困难。这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。
类适配器采用“多继承”的实现方式,一般不推荐使用。而对象适配器采用“对象组合”的方式,更加灵活更符合松耦合的思想。
代码:
#pragma once
//适配器模式
#include <iostream>
using std::cout;
using std::endl;
//用户实际调用的接口
class ITarget
{
public:
virtual void work() = 0;
};
//原有的接口
class IAdaptee
{
public:
virtual void funAdaptee() = 0;
};
//原有接口实现部分
class OldAdaptee : public IAdaptee {
public:
void funAdaptee() override{
cout << "两孔的电器开始工作" << endl;
}
};
//对象型适配器
class Adapter_One : public ITarget
{
public:
Adapter_One(IAdaptee* Adaptee) {
m_Adaptee = Adaptee;
}
void work() override {
cout << "三孔的适配器" << endl;
m_Adaptee->funAdaptee();
}
private:
IAdaptee* m_Adaptee;
};
//类适配器
class Adapter_Two : public ITarget, private OldAdaptee {
public:
void funAdaptee() override {
cout << "两孔的电器开始工作" << endl;
}
void work() override {
cout << "三孔的适配器" << endl;
funAdaptee();
}
};
void Test_Adapter()
{
// 测试对象适配器
IAdaptee* o1 = new OldAdaptee();
Adapter_One a1(o1);
a1.work();
std::cout << "******************" << std::endl;
//测试类适配器
Adapter_Two a2;
a2.work();
}
4.2 桥模式(Dridge)
将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立地变化。
当一个抽象可能有多个实现时,通常用继承的来协调它们。抽象类定义对该抽象的接口,而具体的子类则用不同方式加以实现。但此方法不够灵活,继承机制将抽象部分与它的实现部分固定在一起,使得难以对抽象部分和实现部分进行修改、扩充和复用。
适用性:
- 不希望在抽象和它实现部分之间有一个固定的绑定关系。例如:在程序运行时刻实现部分可以被选择或者替换。
- 类的抽象以及它的实现都可以通过生成子类的方法加以扩充。
- 对一个抽象的实现不能分的修改应对客户不产生影响,即客户的代码不需要重新编译。
结构图:
要点总结:
- 桥模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得对象和实现可以沿着各自的维度来变化。各自维度变化是指“子类化”它们。
- 桥模式可有时候类似于多继承方案,多继承方案违背了单一职责原则(即一个类只有一个变化的原因),复用性比较差。
代码:
//桥模式
#include <iostream>
using std::cout;
using std::endl;
//游戏运行在多个平台上
class IPlatform {
public:
virtual void playgame() = 0;
};
class IGame {
protected:
IPlatform* m_platform;
public:
IGame(IPlatform* platform) {
m_platform = platform;
}
virtual void funcstart() = 0;
};
class IOSpf : public IPlatform {
public:
void playgame() override {
cout << "IOS上游戏开始了" << endl;
}
};
class Andpf : public IPlatform {
public:
void playgame() override {
cout << "And上游戏开始了" << endl;
}
};
class LolGame : public IGame {
public:
LolGame(IPlatform* platform) : IGame(platform){
}
void funcstart() override {
cout << "这是lol游戏" << endl;
m_platform->playgame();
}
};
void Test_Bridge() {
Andpf *pa = new Andpf();
LolGame* pg1 = new LolGame(pa);
pg1->funcstart();
}
4.3 组合模式(Composite)
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
常常有一些组件在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大的破坏组件的复用。这时候,将这些特定数据结构封装在内部,在外部提供统一的接口,来实现与特定数据结构无关的访问,是一种行之有效的解决方案。
适用性:
- 你想表示对象的部分-整体层次结构。
- 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
结构图:
要点总结:
- Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致地(复用)处理对象和对象容器,无需关心处理的是单个的对象还是组合的对象容器。
- 将“客户代码与复杂的对象容器结构”解耦是Composite的核心思想,解耦之后,客户代码将与纯粹的抽象接口(而非对象容器的内部实现结构)发生依赖,从而更能“应对变化”。
- Composite模式在具体的实现中,可以让父对象中的子对象进行反向追溯;如果父对象有频繁的遍历需求,可以使用缓存技巧来改善效率。
代码:
//组合模式
#include <iostream>
#include <list>
#include <string>
#include <algorithm>
using namespace std;
class Component {
public:
virtual void process() = 0;
virtual ~Component() {}
};
//树节点
class Composite : public Component {
private:
string name;
list<Component*> elements;
public:
Composite(const string& s) : name(s) {}
void add(Component* element) {
elements.push_back(element);
}
void remove(Component* element) {
elements.remove(element);
}
void process() override {
//调用该组合中的所有对象
for (auto& e : elements) {
e->process();
}
}
};
//叶子节点
class Leaf :public Component {
private:
string name;
public:
Leaf(string s) : name(s) {}
void process() {
//...
}
};
void Invoke(Component& c) {
c.process();
}
void Test_Composite(){
Composite root("root");
Composite treeNode1("treeNode1");
Composite treeNode2("treeNode2");
Composite treeNode3("treeNode3");
Composite treeNode4("treeNode4");
Leaf leaf1("leaf1");
Leaf leaf2("leaf2");
root.add(&treeNode1);
treeNode1.add(&treeNode2);
treeNode1.add(&leaf1);
root.add(&treeNode3);
treeNode3.add(&treeNode4);
treeNode4.add(&leaf2);
Invoke(root);
}
4.4 装饰器模式(Decorator)
动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码&减少子类个数)。
在某些情况下我们可能会“过度地使用继承来拓展对象的功能”,由于继承为类型引入了静态的特质,使得这种拓展方式缺乏灵活性;并且随着子类的增多(拓展功能的增多),各种子类的组合(拓展功能的组合)会导致更多子类的膨胀。
适用性:
- 在不影响其他的对象的情况下,以动态、透明的方式给单个对象添加职责。
- 处理那些可以撤销的职责。
- 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的拓展,为支持每一种组合将产生大量的子类,使子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
结构图:
要点总结:
- 通过采用对象组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”。
- Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。
- Decorator模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”--是为“装饰”的含义。
代码:
#include <iostream>
//业务操作
class Stream {
public:
virtual char Read(int num) = 0; //读操作
virtual void Seek(int position) = 0; //查找操作
virtual void Write(char data) = 0; //写操作
~Stream() {}
};
//主体类
//文件流
class FileStream : public Stream {
public:
char Read(int num) override {}; //读操作
void Seek(int position) override {}; //查找操作
void Write(char data) override {}; //写操作
};
//网络流
class NetworkStream : public Stream {
public:
char Read(int num) override {}; //读操作
void Seek(int position) override {}; //查找操作
void Write(char data) override {}; //写操作
};
//内存流
class MemoryStream : public Stream{
public:
char Read(int num) override {}; //读操作
void Seek(int position) override {}; //查找操作
void Write(char data) override {}; //写操作
};
//拓展操作,装饰器
class DecoratorStream : public Stream {
protected:
Stream* _stream;
DecoratorStream(Stream* stm) :_stream(stm) {}
};
//其通过构造函数传参,参数为FileStream或NetworkStream或MemoryStream
class CryptoStream : public DecoratorStream {
public:
CryptoStream(Stream* stm) : DecoratorStream(stm) {}
virtual char Read(int num) {
//额外的加密操作。。。
_stream->Read(num); //读文件流
}
virtual void Seek(int position) {
//额外加密操作。。。
_stream->Seek(position);
}
virtual void Write(char data) {
//额外的加密操作
_stream->Write(data); //写文件流
}
};
4.5 门面模式(Facade)
为子系统中的一组接口提供一个一致(稳定)的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用(复用)。
结构图:
4.6 享元模式(Flyweight)
运用共享技术有效地支持大量细粒度的对象。
核心就是如果对象是可以共享的,则利用类似对象池(里面的对象是可以共享的),避免对象被大量多次创建。
在软件系统采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行代价(主要是内存方面的代价)。
结构图:
代码:
#include <iostream>
#include <string>
#include <map>
//字体类,字体是可以共享的
class Font {
private:
std::string key; //字体的标识字段
public:
Font(const std::string& key) {
}
};
//字体的创建工厂
class FontFactory {
private:
//对象池
std::map<std::string, Font*> fontpool;
public:
Font* GetFont(const std::string& key) {
std::map<std::string, Font*>::iterator item = fontpool.find(key);
if (item != fontpool.end()) {
return fontpool[key];
}
else {
Font* font = new Font(key);
fontpool[key] = font;
return font;
}
}
};
4.7 代理模式(Proxy)
为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。
在面向对象系统中,有些对象由于某种原因(比如对象创建的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者、或者系统结构带来很多的麻烦。
适用性:
在需要用比较通用和复杂的对象指针代替简单对象的指针的时候。
- 远程代理,为一个对象在不同的地址空间提供局部代表。
- 虚代理,根据需要创建开销很大的对象。
- 保护代理,控制对原始对象的访问,保护代理用于对象应该有不同的访问权限的时候。
- 智能指针,取代简单的指针,它在访问对象时执行一些附加操作,如:对指向实际对象的引用计数,当该对象没有引用时,自动释放(shared_ptr);当第一次引用一个持久对象时,将它装入内存;在访问一个实际对象前,检测是否已经锁住了它(类似将对象加锁进行保护)。
要点总结:
- “增加一层间接层”是软件系统中对许多复杂问题的一种常见解决方法。在面向对象系统中,直接使用某些对象会带来很多问题,作为间接层的proxy对象便是解决这一问题的常用手段。
- 具体的proxy设计模式的实现方法、实现粒度都相差很大,有些可能对单个对象做细粒度的控制,如copy-on-write技术,有些可能对组件模块提供抽象代理层,在架构层次对对象做proxy。
- proxy并不一定要求保持接口完整的一致性,只要能够实现间接控制,有时损失一些透明性也是可以接受的。
4.8 结构型模式讨论
4.8.1 Adapter与Bridge
Adapter模式主要是为了解决两个已有接口之间的不匹配问题,它不考虑这些接口是怎样实现的,也不考虑它们各自可能会如何变化。
Bridge模式则对抽象接口与它的(可能是多个)实现部分进行桥接。
Facade是另外一组对象的适配器。
4.8.2 Composite、Decorator与Proxy
Decorator能够不生成子类即可给对象添加职责。这就避免了静态实现所有功能组合,从而导致子类急剧增加。
Composite旨在构造类,使多个相关的对象能够以统一的方式处理,而多重对象可以当作一个对象来处理,它的重点不在于修饰,而是在于表示。
Proxy模式构成一个对象并为用户提供一致性的接口。在Proxy模式中,实体定义了关键功能,而Proxy提供(或拒绝)对它的访问。在Decorator模式中,组件仅提供了部分功能,而一个或多个Decorator负责完成其他功能。Decorator模式适用于编译时不能(至少是不方便)确定对象的全部功能的情况。
5 行为模式(Behavioral)
行为模式涉及到算法和对象间职责分配,行为模式不仅描述对象或类的模式,还描述它们之间的通信模式。
行为类模式使用继承机制在类间进行分派行为。包括TemplateMethod和Interpreter两种。
行为对象模式使用对象复合而不是继承,一些行为对象模式描述了一组对等的对象怎样相互协作以完成其中任一对象都无法完成的任务。
5.1 职责链模式(Chain of Responsibility)
使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
一句话,将所有能处理请求的对象都连成一个链表(或其他数据结构),当一个请求来时,在该链表进行遍历,直至有处理对象能处理该请求。
要点总结:
- 职责链模式适用于“一个请求有多个接收者,但是最后真正的接受者只有一个”,这时候发送请求者与接受者的耦合有可能出现“变化脆弱”的症状,职责链的目的就是将二者进行解耦。
- 应用职责链模式之后,对象的职责分派将更加具有灵活性。可以在运行时动态添加或修改请求处理的职责。
- 如果请求传递到末尾仍得不到处理,应有有一个缺省的机制。
结构图:
代码:
#include <iostream>
#include <string>
//enum class 强类型枚举,不同的枚举类型之间不支持隐士类型转换
enum class EnumReqType
{
Req_1,
Req_2,
Req_3
};
class Request {
private:
std::string _description;
EnumReqType _reqType;
public:
Request(const std::string& desc, EnumReqType type) :_description(desc), _reqType(type) {}
EnumReqType getType() const{
return _reqType;
}
const std::string& getDescription() {
return _description;
}
};
class IChainHandler {
private:
IChainHandler* _nextChain;
void sendReqToNext(const Request& req) {
if (_nextChain != nullptr);
_nextChain->handle(req);
}
protected:
virtual bool canHandle(const Request& req) = 0;
virtual void processHandle(const Request& req) = 0;
public:
IChainHandler() { _nextChain == nullptr; }
void setNextChain(IChainHandler* nextchain) {
_nextChain = nextchain;
}
void handle(const Request& req) {
if (canHandle(req)) //如果能处理该请求,则进行处理
processHandle(req);
else //不能处理该请求则发送给下一个
sendReqToNext(req);
}
};
class Handler1 : public IChainHandler {
protected:
bool canHandle(const Request& req) override {
return req.getType() == EnumReqType::Req_1;
}
void processHandle(const Request& req) override {
std::cout << "Handler1 处理请求" << std::endl;
}
};
class Handler2 : public IChainHandler {
protected:
bool canHandle(const Request& req) override {
return req.getType() == EnumReqType::Req_2;
}
void processHandle(const Request& req) override {
std::cout << "Handler2 处理请求" << std::endl;
}
};
class Handler3 : public IChainHandler {
protected:
bool canHandle(const Request& req) override {
return req.getType() == EnumReqType::Req_3;
}
void processHandle(const Request& req) override {
std::cout << "Handler3 处理请求" << std::endl;
}
};
void Test_Chain() {
Handler1 h1;
Handler2 h2;
Handler3 h3;
h1.setNextChain(&h2);
h2.setNextChain(&h3);
Request req("这是一个请求", EnumReqType::Req_2);
h1.handle(req);
}
如果将所有的的handler放在一个容器里面(比如链表),这样处理获取会更方便。
5.2 命令模式(Command)
将一个请求(行为)封装成一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及可支持可撤销的操作。(类似将函数对象,一个对象重载()运算符)。
有时必须向某对象提交请求,但并不知道关于被请求的操作或请求的接收者的任何信息。命令模式通过将本身变成一个对象来使工具箱对象可向未指定的应用对象提出请求。这个对象可被存储并像其他的对象一样被传递。用一个抽象的Command类,它定义一个执行操作的接口。
结构图:
要点总结:
- Command模式的根本目的在于将“行为的请求者”和“行为的实现者”进行解耦,在面向对象的语言中,方法是“将行为抽象为对象”。
- Command模式与C++中的函数对象有些类似。但二者定义行为接口的规范是有所区别的:Command以面向对象中的“接口-实现”来定义行为接口规范,更严格,但是性能有所损失(有虚函数等);C++函数对象以函数签名来定义接口规范,更加灵活,性能更高。
#include <iostream>
#include <vector>
class ICommand {
public:
virtual void execute() = 0;
};
class Command1 : public ICommand {
public:
void execute() override {
std::cout << "#1...Command1" << std::endl;
}
};
class Command2 : public ICommand {
public:
void execute() override {
std::cout << "#2...Command2" << std::endl;
}
};
class CommandVector : public ICommand{
private:
std::vector<ICommand*> commands;
public:
void addCommand(ICommand* c) {
commands.push_back(c);
}
void execute() override {
for (auto& c : commands) {
c->execute();
}
}
};
void Test_Command() {
Command1 c1;
Command2 c2;
CommandVector cv;
cv.addCommand(&c1);
cv.addCommand(&c2);
cv.execute();
}
5.3 解释器模式(Interpreter)
解释器模式,属于类行为型模式。给定一个语言,定义它的文法的一种表示,并定义一种解释器,这个解释器使用该表示来解释语言中的句子。
当有一个语言需要解释执行,并且可以将该语言中的句子表示为一个抽象的语法树时,可以使用语法树模式。高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转换成另一种形式(正则表达式通常被转换成状态机)。
代码:
//解释器模式
#include <iostream>
#include <map>
#include <stack>
class Expression {
public:
virtual int interpreter(std::map<char, int> var) = 0;
virtual ~Expression() {}
};
//变量表达式
class VarExpression : public Expression {
private:
char key;
public:
VarExpression(const char& key) {
this->key = key;
}
int interpreter(std::map<char, int> var) override {
return var[key];
}
};
//符号表达式
class SymbolExpression : public Expression {
protected:
//运算符的左参数和右参数
Expression* _left;
Expression* _right;
public:
SymbolExpression(Expression* left, Expression* right) {
_left = left;
_right = right;
}
};
//加法运算
class AddExpression : public SymbolExpression {
public:
AddExpression(Expression* left, Expression* right) :
SymbolExpression(left, right) {}
int interpreter(std::map<char, int> var) override {
return _left->interpreter(var) + _right->interpreter(var);
}
};
//减法运算
class SubExpression : public SymbolExpression {
public:
SubExpression(Expression* left, Expression* right) :
SymbolExpression(left, right) {}
int interpreter(std::map<char, int> var) override {
return _left->interpreter(var) - _right->interpreter(var);
}
};
//解析函数
Expression* analyse(std::string expstr) {
std::stack<Expression*> expStack;
Expression* _left = nullptr;
Expression* _right = nullptr;
for (int i = 0; i < expstr.size(); i++) {
switch (expstr[i])
{
case '+':
//加法运算
_left = expStack.top();
_right = new VarExpression(expstr[++i]);
expStack.push(new AddExpression(_left, _right));
break;
case '-':
_left = expStack.top();
_right = new VarExpression(expstr[++i]);
expStack.push(new SubExpression(_left, _right));
break;
default:
//变量表达式
expStack.push(new VarExpression(expstr[i]));
break;
}
}
Expression* expression = expStack.top();
return expression;
}
void release(Expression* exp) {
//释放内存
}
void Test_Interpreter() {
std::string expstr = "a+b-c+d-e";
std::map<char, int> var;
var.insert(std::make_pair('a', 1));
var.insert(std::make_pair('b', 3));
var.insert(std::make_pair('c', 5));
var.insert(std::make_pair('d', 7));
var.insert(std::make_pair('e', 9));
Expression* expression = analyse(expstr);
int r = expression->interpreter(var);
std::cout << "r = " << r << std::endl;
release(expression);
}
5.4 迭代器模式(Iterator)
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露(稳定)该对象的内部表示。
设计模式中的迭代器采用的时面向对象的迭代器模式,在新的C++(泛型编程)出现以后,面向对象的模式由于效率不高而被取代。利用模板是编译时多态,效率要高于运行时多态(面向对象的迭代器主要利用虚函数,效率相对较低)。
在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部用户代码透明地访问其中包含的元素;同时这种“透明遍历”也为“同一种算法在多重集合对象上进行操作”提供了可能。
要点总结:
- 迭代抽象:访问一个对象的内容而无需暴露它的内部表示。
- 迭代多态:为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。
- 迭代器健壮性:遍历的同时更改迭代器所在的集合结构,有可能会导致问题。
结构图:
5.5 中介者模式(Mediator)
用一个中介对象来封装(封装变化)一系列的对象交互。中介者使各对象不需要显式的相互引用(编译时依赖-->运行时依赖),从而使其耦合松散(管理变化),而且可以独立地改变它们之间的交互。
在软件构建过程中,经常会出现多个对象相互关联交互的情况,对象之间常常会维持一种复杂的引用关系,如果遇到一些需求的更改,这种直接的引用关系将面临不断的变化。在这种情况下,我们可使用一个“中介对象”来管理对象间的关联关系,避免相互交互的对象之间的紧耦合的关系,从而更好地抵御变化。
结构图:
要点总结:
- 将多个对象间复杂的关联关系解耦,Mediator模式将多个对象间的控制逻辑进行集中管理,变“多个对象互相关联”为“多个对象和一个中介者关联”,简化了系统维护,抵御变化。
- 随着控制逻辑的复杂化,Mediator具体对象的实现可能相当复杂,这时候可以对Mediator对象进行分解处理。
- Facade模式是解耦系统间(单向)的对象关系;Mediator模式是解耦系统内各个对象之间(双向)的关联关系。
5.6 备忘录模式(Memento)
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
类似在软件操作过程中Undo和Redo操作。
结构图:
- 备忘录(Memento)存储原发器(Orientator)对象的内部状态,在需要时恢复原发器状态。
- Memento模式的核心是信息的隐藏,即Originator需要向外界隐藏信息,保持其封装性。但同时又需要将状态保存到外界(Memento)。
- 由于现代语言运行时(如C#,Java等)都具有相当的对象序列化支持,因此往往采用效率较高、又较容易正确实现的序列化方案来实现Memento模式。
5.7 观察者模式(Observer)
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
一个目标有任意多个观察者,一旦目标的状态发生改变时,所有的观察者都会得到通知并作出相应的反应。作为对这个通知的相应,每个观察者都将查询目标以使其状态与目标的状态同步。
结构图:
代码:
#include <iostream>
#include <list>
//using namespace std;
class IObserver {
public:
virtual void dofunc(int val) = 0;
};
//目标对象,当目标对象发生改变时,会通知所有的观察者
class ISubject {
protected:
std::list<IObserver*> listObs; //观察者
public:
virtual void addObs(IObserver* obs) {
listObs.push_back(obs);
}
virtual void delObs(IObserver* obs) {
listObs.remove(obs);
}
virtual void funcNotify() = 0; //通知函数
};
//具体的目标
class SubVal : public ISubject{
public:
SubVal(int val = 0) : _val(val) { }
void funcNotify() override {
std::list<IObserver*>::iterator it = listObs.begin();
for (; it != listObs.end(); it++) {
(*it)->dofunc(getVal());
}
}
void setVal(int val) {
if (val != _val) {
_val = val;
funcNotify();
}
}
int getVal() {
return _val;
}
private:
int _val;
};
class Obs_1 : public IObserver {
public:
void dofunc(int val) override {
std::cout << "1 Obs_1 Subject Val = " << val << std::endl;
}
};
class Obs_2 : public IObserver {
public:
void dofunc(int val) override {
std::cout << "2 Obs_2 Subject Val = " << val << std::endl;
}
};
class Obs_3 : public IObserver {
public:
void dofunc(int val) override {
std::cout << "3 Obs_3 Subject Val = " << val << std::endl;
}
};
void Test_Observer() {
SubVal sv;
Obs_1 o1;
Obs_2 o2;
Obs_3 o3;
sv.addObs(&o1);
sv.addObs(&o2);
sv.addObs(&o3);
sv.setVal(85);
std::cout << "******" << std::endl;
sv.delObs(&o2);
sv.setVal(65);
}