设计模式大纲
-
创建型模式:用来描述 “如何创建对象”,它的主要特点是 “将对象的创建和使用分离”。包括单例、原型、工厂方法、抽象工厂和建造者 5 种模式。
-
结构型模式:用来描述如何将类或对象按照某种布局组成更大的结构。包括代理、适配器、桥接、装饰、外观、享元和组合 7 种模式。
-
行为型模式:用来识别对象之间的常用交流模式以及如何分配职责。包括模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录和解释器 11 种模式。
创造型模式
单例模式
单例模式作为最常用的设计模式之一,保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
实现思路:私有化它的构造函数,以防止外界创建单例类的对象;使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。
单例模式有两种实现方法,分别是懒汉和饿汉模式。顾名思义,懒汉模式,即非常懒,不用的时候不去初始化,所以在第一次被使用时才进行初始化;饿汉模式,即迫不及待,在程序运行时立即初始化。
经典的线程安全懒汉模式
单例模式的实现思路如前述所示,其中,经典的线程安全懒汉模式,使用双检测锁模式。
class single{ private: static single *p;
static pthread_mutex_t lock;
single(){ pthread_mutex_init(&lock, NULL); } ~single(){}
public: static single* getinstance(); };
pthread_mutex_t single::lock; single* single::p = NULL;
single* single::getinstance(){ if (NULL == p){ pthread_mutex_lock(&lock); if (NULL == p){ p = new single; } pthread_mutex_unlock(&lock); } return p; }
|
为什么要用双检测,只检测一次不行吗?
如果只检测一次,在每次调用获取实例的方法时,都需要加锁,这将严重影响程序性能。双层检测可以有效避免这种情况,仅在第一次创建单例的时候加锁,其他时候都不再符合NULL == p的情况,直接返回已创建好的实例。
局部静态变量之线程安全懒汉模式
前面的双检测锁模式,写起来不太优雅,《Effective C++》(Item 04)中的提出另一种更优雅的单例模式实现,使用函数内的局部静态对象,这种方法不用加锁和解锁操作。
class single{ private: single(){} ~single(){}
public: static single* getinstance(); };
single* single::getinstance(){ static single obj; return &obj; }
|
这时候有人说了,这种方法不加锁会不会造成线程安全问题?
其实,C0X以后,要求编译器保证内部静态变量的线程安全性,故C0x之后该实现是线程安全的,C0x之前仍需加锁,其中C0x是C++11标准成为正式标准之前的草案临时名字。
所以,如果使用C++11之前的标准,还是需要加锁,这里同样给出加锁的版本。
class single { private: static pthread_mutex_t lock;
single() { pthread_mutex_init(&lock, NULL); }
~single() {}
public: static single* getinstance(); };
pthread_mutex_t single::lock;
single* single::getinstance() { pthread_mutex_lock(&lock); static single obj; pthread_mutex_unlock(&lock); return &obj; }
|
静态变量在C++中具有特定的作用域和生命周期,这些特性与普通局部变量或全局变量有所不同。
-
作用域(Scope):
- 静态局部变量(局部静态变量):静态局部变量的作用域限于定义它的函数内,它在函数内可见,但在函数外不可访问。
- 静态全局变量:静态全局变量的作用域是整个文件(或编译单元),即它在定义它的文件内可见,但在其他文件中不可访问。
-
生命周期(Lifetime):
- 静态局部变量:静态局部变量的生命周期贯穿整个程序的执行过程,从首次初始化到程序结束。它会一直存在于内存中,而不是在函数退出时销毁,这使得它可以在多次函数调用之间保持其值。
- 静态全局变量:静态全局变量的生命周期也贯穿整个程序的执行过程,从程序启动到结束。与普通全局变量不同,静态全局变量只在定义它的文件内可见。
静态变量适用于需要在多次函数调用之间保持状态的情况,因为它们的值在函数调用之间保持不变,且不会被销毁。它们也用于实现单例模式或共享状态的情况。
需要注意的是,静态变量的初始化只会发生一次,无论是静态局部变量还是静态全局变量,它们的值在首次初始化后将保持不变。
饿汉模式
饿汉模式不需要用锁,就可以实现线程安全。原因在于,在程序运行时就定义了对象,并对其初始化。之后,不管哪个线程调用成员函数getinstance(),都只不过是返回一个对象的指针而已。所以是线程安全的,不需要在获取实例的成员函数中加锁。
class single { private: static single* p; single() {} ~single() {}
public: static single* getinstance(); };
single* single::p = new single();
single* single::getinstance() { return p; }
int main() { single* p1 = single::getinstance(); single* p2 = single::getinstance();
if (p1 == p2) { cout << "same" << endl; }
system("pause"); return 0; }
|
饿汉模式虽好,但其存在隐藏的问题,在于非静态对象(函数外的static对象)在不同编译单元中的初始化顺序是未定义的。如果在初始化完成之前调用 getInstance() 方法会返回一个未定义的实例。
使用场景
-
资源共享
多个模块共享某个资源的时候,可以使用单例模式,比如说应用程序需要一个全局的配置管理器来存储和管理配置信息、亦或是使用单例模式管理数据库连接池。
-
只有一个实例
当系统中某个类只需要一个实例来协调行为的时候,可以考虑使用单例模式, 比如说管理应用程序中的缓存,确保只有一个缓存实例,避免重复的缓存创建和管理,或者使用单例模式来创建和管理线程池。
-
懒加载
如果对象创建本身就比较消耗资源,而且可能在整个程序中都不一定会使用,可以使用单例模式实现懒加载。
工厂模式
我们先看工厂模式的介绍
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
简单来说,使用了C++多态的特性,将存在继承关系的类,通过一个工厂类创建对应的子类(派生类)对象。在项目复杂的情况下,可以便于子类对象的创建。
工厂模式的实现方式可分别简单工厂模式、工厂方法模式、抽象工厂模式,每个实现方式都存在优和劣。
最近炒鞋炒的非常的火,那么以鞋厂的形式,一一分析针对每个实现方式进行分析。
简单工厂模式
具体的情形:
鞋厂可以指定生产耐克、阿迪达斯和李宁牌子的鞋子。哪个鞋炒的火爆,老板就生产哪个,看形势生产。
UML图:
简单工厂模式的结构组成:
-
工厂类:工厂模式的核心类,会定义一个用于创建指定的具体实例对象的接口。
-
抽象产品类:是具体产品类的继承的父类或实现的接口。
-
具体产品类:工厂类所创建的对象就是此具体产品实例。
简单工厂模式的特点:
工厂类封装了创建具体产品对象的函数。
简单工厂模式的缺陷:
扩展性非常差,新增产品的时候,需要去修改工厂类。
简单工厂模式的代码:
Shoes为鞋子的抽象类(基类),接口函数为Show(),用于显示鞋子广告。
NiKeShoes、AdidasShoes、LiNingShoes为具体鞋子的类,分别是耐克、阿迪达斯和李宁鞋牌的鞋,它们都继承于Shoes抽象类。
class Shoes { public: virtual ~Shoes() {} virtual void Show() = 0; };
class NiKeShoes : public Shoes { public: void Show() { std::cout << "我是耐克球鞋,我的广告语:Just do it" << std::endl; } };
class AdidasShoes : public Shoes { public: void Show() { std::cout << "我是阿迪达斯球鞋,我的广告语:Impossible is nothing" << std::endl; } };
class LiNingShoes : public Shoes { public: void Show() { std::cout << "我是李宁球鞋,我的广告语:Everything is possible" << std::endl; } };
|
ShoesFactory为工厂类,类里实现根据鞋子类型创建对应鞋子产品对象的CreateShoes(SHOES_TYPE type)函数。
enum SHOES_TYPE { NIKE, LINING, ADIDAS };
class ShoesFactory { public: Shoes *CreateShoes(SHOES_TYPE type) { switch (type) { case NIKE: return new NiKeShoes(); break; case LINING: return new LiNingShoes(); break; case ADIDAS: return new AdidasShoes(); break; default: return NULL; break; } } };
|
main函数,先是构造了工厂对象,后创建指定类型的具体鞋子产品对象,创建了具体鞋子产品的对象便可直接打印广告。因为采用的是new
的方式创建了对象,用完了要通过delete
释放资源资源哦!
int main() { ShoesFactory shoesFactory;
Shoes *pNikeShoes = shoesFactory.CreateShoes(NIKE); if (pNikeShoes != NULL) { pNikeShoes->Show();
delete pNikeShoes; pNikeShoes = NULL; }
Shoes *pLiNingShoes = shoesFactory.CreateShoes(LINING); if (pLiNingShoes != NULL) { pLiNingShoes->Show();
delete pLiNingShoes; pLiNingShoes = NULL; }
Shoes *pAdidasShoes = shoesFactory.CreateShoes(ADIDAS); if (pAdidasShoes != NULL) { pAdidasShoes->Show();
delete pAdidasShoes; pAdidasShoes = NULL; }
return 0; }
|
输出结果:
[root@lincoding factory]# ./simpleFactory 我是耐克球鞋,我的广告语:Just do it 我是阿迪达斯球鞋,我的广告语:Impossible is nothing 我是李宁球鞋,我的广告语:Everything is possible
|
工厂方法模式
具体情形:
现各类鞋子抄的非常火热,于是为了大量生产每种类型的鞋子,则要针对不同品牌的鞋子开设独立的生产线,那么每个生产线就只能生产同类型品牌的鞋。
UML图:
工厂方法模式的结构组成:
-
抽象工厂类:工厂方法模式的核心类,提供创建具体产品的接口,由具体工厂类实现。
-
具体工厂类:继承于抽象工厂,实现创建对应具体产品对象的方式。
-
抽象产品类:它是具体产品继承的父类(基类)。
-
具体产品类:具体工厂所创建的对象,就是此类。
工厂方法模式的特点:
工厂方法模式的缺陷:
工厂方法模式的代码:
ShoesFactory抽象工厂类,提供了创建具体鞋子产品的纯虚函数。
NiKeProducer、AdidasProducer、LiNingProducer
具体工厂类,继承持续工厂类,实现对应具体鞋子产品对象的创建。
class ShoesFactory { public: virtual Shoes *CreateShoes() = 0; virtual ~ShoesFactory() {} };
class NiKeProducer : public ShoesFactory { public: Shoes *CreateShoes() { return new NiKeShoes(); } };
class AdidasProducer : public ShoesFactory { public: Shoes *CreateShoes() { return new AdidasShoes(); } };
class LiNingProducer : public ShoesFactory { public: Shoes *CreateShoes() { return new LiNingShoes(); } };
|
main函数针对每种类型的鞋子,构造了每种类型的生产线,再由每个生产线生产出对应的鞋子。需注意的是具体工厂对象和具体产品对象,用完了需要通过delete释放资源。
int main() { ShoesFactory *niKeProducer = new NiKeProducer(); Shoes *nikeShoes = niKeProducer->CreateShoes(); nikeShoes->Show(); delete nikeShoes; delete niKeProducer;
ShoesFactory *adidasProducer = new AdidasProducer(); Shoes *adidasShoes = adidasProducer->CreateShoes(); adidasShoes->Show(); delete adidasShoes; delete adidasProducer;
return 0; }
|
输出结果:
[root@lincoding factory]# ./methodFactory 我是耐克球鞋,我的广告语:Just do it 我是阿迪达斯球鞋,我的广告语:Impossible is nothing
|
抽象工厂模式
具体情形:
鞋厂为了扩大了业务,不仅只生产鞋子,把运动品牌的衣服也一起生产了。
UML图:
抽象工厂模式的结构组成(和工厂方法模式一样):
-
抽象工厂类:工厂方法模式的核心类,提供创建具体产品的接口,由具体工厂类实现。
-
具体工厂类:继承于抽象工厂,实现创建对应具体产品对象的方式。
-
抽象产品类:它是具体产品继承的父类(基类)。
-
具体产品类:具体工厂所创建的对象,就是此类。
抽象工厂模式的特点:
提供一个接口,可以创建多个产品族中的产品对象。如创建耐克工厂,则可以创建耐克鞋子产品、衣服产品、裤子产品等。
抽象工厂模式的缺陷:
同工厂方法模式一样,新增产品时,都需要增加一个对应的产品的具体工厂类。
抽象工厂模式的代码:
Clothe和Shoes,分别为衣服和鞋子的抽象产品类。
NiKeClothe和NiKeShoes,分别是耐克衣服和耐克衣服的具体产品类。
class Clothe { public: virtual void Show() = 0; virtual ~Clothe() {} };
class NiKeClothe : public Clothe { public: void Show() { std::cout << "我是耐克衣服,时尚我最在行!" << std::endl; } };
class Shoes { public: virtual void Show() = 0; virtual ~Shoes() {} };
class NiKeShoes : public Shoes { public: void Show() { std::cout << "我是耐克球鞋,让你酷起来!" << std::endl; } };
|
Factory为抽象工厂,提供了创建鞋子CreateShoes()和衣服产品CreateClothe()对象的接口。
NiKeProducer为具体工厂,实现了创建耐克鞋子和耐克衣服的方式。
class Factory { public: virtual Shoes *CreateShoes() = 0; virtual Clothe *CreateClothe() = 0; virtual ~Factory() {} };
class NiKeProducer : public Factory { public: Shoes *CreateShoes() { return new NiKeShoes(); } Clothe *CreateClothe() { return new NiKeClothe(); } };
|
main函数,构造耐克工厂对象,通过耐克工厂对象再创建耐克产品族的衣服和鞋子对象。同样,对象不再使用时,需要手动释放资源。
int main() { Factory *niKeProducer = new NiKeProducer(); Shoes *nikeShoes = niKeProducer->CreateShoes(); Clothe *nikeClothe = niKeProducer->CreateClothe(); nikeShoes->Show(); nikeClothe->Show(); delete nikeShoes; delete nikeClothe; delete niKeProducer;
return 0; }
|
输出结果:
[root@lincoding factory]# ./abstractFactory 我是耐克球鞋,让你酷起来! 我是耐克衣服,时尚我最在行!
|
总结
以上三种工厂模式,在新增产品时,都存在一定的缺陷。
那么有什么好的方法,在新增产品时,即不用修改工厂类,也不用新增具体的工厂类?详细内容可以跳转至 C++ 深入浅出工厂模式(进阶篇)
建造者模式
建造者模式(也被成为⽣成器模式),是⼀种创建型设计模式,软件开发过程中有的时候需要创建很复杂的对象, ⽽建造者模式的主要思想是将对象的构建过程分为多个步骤,并为每个步骤定义⼀个抽象的接⼝。具体的构建过程 由实现了这些接⼝的具体建造者类来完成。同时有⼀个指导者类负责协调建造者的⼯作,按照⼀定的顺序或逻辑来 执⾏构建步骤,最终⽣成产品。
基本结构
建造者模式有下⾯⼏个关键⻆⾊:
-
产品Product:被构建的复杂对象, 包含多个组成部分。
-
抽象建造者 Builder : 定义构建产品各个部分的抽象接⼝和⼀个返回复杂产品的⽅法
-
具体建造者 getResult Concrete Builder :实现抽象建造者接⼝,构建产品的各个组成部分,并提供⼀个⽅法返回最 终的产品。
-
指导者 Director :调⽤具体建造者的⽅法,按照⼀定的顺序或逻辑来构建产品。
在客户端中,通过指导者来构建产品,而并不和具体建造者进行直接的交互。
简易实现
建造者模式的实现步骤通常包括以下几个阶段
-
定义产品类:产品类应该包含多个组成部分,这些部分的属性和方法构成了产品的接口
class Product { private: std::string part1; std::string part2;
public: void setPart1(const std::string& part1) { this->part1 = part1; }
void setPart2(const std::string& part2) { this->part2 = part2; }
void showProduct() const { std::cout << "Product with Part1: " << part1 << " and Part2: " << part2 << std::endl; } };
|
-
定义抽象建造者接口:创建一个接口,包含构建产品各个部分的抽象方法。这些方法通常用于设置产品的各个属性。
class Builder { public: virtual ~Builder() {} virtual void buildPart1(const std::string& part1) = 0; virtual void buildPart2(const std::string& part2) = 0; virtual Product* getResult() = 0; };
|
-
创建具体建造者:实现抽象建造者接口,构建具体的产品。
class ConcreteBuilder : public Builder { private: Product* product;
public: ConcreteBuilder() { product = new Product(); }
~ConcreteBuilder() { delete product; }
void buildPart1(const std::string& part1) override { product->setPart1(part1); }
void buildPart2(const std::string& part2) override { product->setPart2(part2); }
Product* getResult() override { return product; } };
|
-
定义Director
类: 指导者类来控制构建产品的顺序和步骤。
class Director { private: Builder* builder;
public: Director(Builder* builder) { this->builder = builder; }
void construct() { builder->buildPart1("Part1"); builder->buildPart2("Part2"); } };
|
-
客户端使用建造者模式:在客户端中创建【具体建造者对象】和【指导者对象】,通过指导者来构建产品。
int main() { Builder* builder = new ConcreteBuilder();
Director director(builder);
director.construct();
Product* product = builder->getResult();
product->showProduct();
delete builder;
return 0; }
|
使用场景
使⽤建造者模式有下⾯⼏处优点:
对应的,建造者模式适⽤于复杂对象的创建,当对象构建过程相对复杂时可以考虑使⽤建造者模式,但是当产品的 构建过程发⽣变化时,可能需要同时修改指导类和建造者类,这就使得重构变得相对困难。
原型模式
原型模式⼀种创建型设计模式,该模式的核⼼思想是基于现有的对象创建新的对象,⽽不是从头开始创建。 在原型模式中,通常有⼀个原型对象,它被⽤作创建新对象的模板。新对象通过复制原型对象的属性和状态来创 建,⽽⽆需知道具体的创建细节
基本结构
实现原型模式需要给【原型对象】声明一个克隆方法,执行该方法会创建一个当前类的新对象,并将原始对象中的成员变量复制到新生成的对象中,而不必实例化。并且在这个过程中只需要调用原型对象的克隆方法,而无需知道原型对象的具体类型。
原型模式包含两个重点模块:
在客户端代码中,可以声明一个具体原型类的对象,然后调用clone()
方法复制原对象生成一个新的对象。
基本实现
原型模式的实现过程即上面描述模块的实现过程:
class Prototype { public: virtual ~Prototype() {} virtual Prototype* clone() const = 0; };
class ConcretePrototype : public Prototype { private: std::string data;
public: ConcretePrototype(const std::string& data) : data(data) {}
Prototype* clone() const override { return new ConcretePrototype(*this); }
std::string getData() const { return data; } };
int main() { Prototype* original = new ConcretePrototype("Original Data");
Prototype* clone = original->clone();
ConcretePrototype* concreteClone = dynamic_cast<ConcretePrototype*>(clone); if (concreteClone) { std::cout << "Clone Data: " << concreteClone->getData() << std::endl; }
delete original; delete clone;
return 0; }
|
使用场景
相比于直接实例化对象,通过原型模式复制对象可以减少资源消耗,提高性能,尤其在对象的创建过程复杂或对象的创建代价较大的情况下。当需要频繁创建相似对象、并且可以通过克隆避免重复初始化工作的场景时可以考虑使用原型模式,在克隆对象的时候还可以动态地添加或删除原型对象的属性,创造出相似但不完全相同的对象,提高了灵活性。
但是使用原型模式也需要考虑到如果对象的内部状态包含了引用类型的成员变量,那么实现深拷贝就会变得较为复杂,需要考虑引用类型对象的克隆问题。
结构型模式
适配器模式
什么是适配器
适配器模式Adapter
是一种结构型设计模式,它可以将一个类的接口转换成客户希望的另一个接口,主要目的是充当两个不同接口之间的桥梁,使得原本接口不兼容的类能够一起工作。
基本结构
适配器模式分为以下几个基本角色:
可以把适配器模式理解成拓展坞,起到转接的作用,原有的接口是USB,但是客户端需要使用type-c
, 便使用拓展坞提供一个type-c
接口给客户端使用
这样,客户端就可以使用目标接口,而不需要对原来的Adaptee
进行修改,Adapter
起到一个转接扩展的作用。
基本实现
class Target { public: virtual ~Target() {} virtual void request() = 0; };
#include <iostream>
class Adaptee { public: void specificRequest() { std::cout << "Specific request" << std::endl; } };
class Adapter : public Target { private: Adaptee* adaptee;
public: Adapter(Adaptee* adaptee) { this->adaptee = adaptee; }
void request() override { adaptee->specificRequest(); } };
int main() { Adaptee* adaptee = new Adaptee(); Target* target = new Adapter(adaptee); target->request();
delete target; delete adaptee;
return 0; }
|
应用场景
在开发过程中,适配器模式往往扮演者“补救”和“扩展”的角色:
使用适配器模式可以将客户端代码与具体的类解耦,客户端不需要知道被适配者的细节,客户端代码也不需要修改,这使得它具有良好的扩展性,但是这也势必导致系统变得更加复杂。
代理模式
基本概念
代理模式Proxy Pattern
是一种结构型设计模式,用于控制对其他对象的访问。
在代理模式中,允许一个对象(代理)充当另一个对象(真实对象)的接口,以控制对这个对象的访问。通常用于在访问某个对象时引入一些间接层(中介的作用),这样可以在访问对象时添加额外的控制逻辑,比如限制访问权限,延迟加载。
比如说有一个文件加载的场景,为了避免直接访问“文件”对象,我们可以新增一个代理对象,代理对象中有一个对“文件对象”的引用,在代理对象的 load
方法中,可以在访问真实的文件对象之前进行一些操作,比如权限检查,然后调用真实文件对象的 load
方法,最后在访问真实对象后进行其他操作,比如记录访问日志。
基本结构
代理模式的主要角色有:
-
Subject(抽象主题): 抽象类,通过接口或抽象类声明真实主题和代理对象实现的业务方法。
-
RealSubject(真实主题):定义了Proxy所代表的真实对象,是客户端最终要访问的对象。
-
Proxy(代理):包含一个引用,该引用可以是RealSubject的实例,控制对RealSubject的访问,并可能负责创建和删除RealSubject的实例。
实现方式
代理模式的基本实现分为以下几个步骤:
-
定义抽象主题, 一般是接口或者抽象类,声明真实主题和代理对象实现的业务方法。
class Subject { public: virtual ~Subject() {} virtual void request() = 0; };
|
-
定义真实主题,实现抽象主题中的具体业务
class RealSubject : public Subject { public: void request() override { std::cout << "RealSubject handles the request." << std::endl; } };
|
-
定义代理类,包含对RealSubject
的引用,并提供和真实主题相同的接口,这样代理就可以替代真实主题,并对真实主题进行功能扩展。
class Proxy : public Subject { private: RealSubject* realSubject;
public: Proxy() : realSubject(nullptr) {} ~Proxy() { delete realSubject; }
void request() override { if (realSubject == nullptr) { realSubject = new RealSubject(); } realSubject->request(); } };
|
-
客户端使用代理
int main() { Subject* proxy = new Proxy(); proxy->request();
delete proxy;
return 0; }
|
应用场景
代理模式可以控制客户端对真实对象的访问,从而限制某些客户端的访问权限,此外代理模式还常用在访问真实对象之前或之后执行一些额外的操作(比如记录日志),对功能进行扩展。
以上特性决定了代理模式在以下几个场景中有着广泛的应用:
但是代理模式涉及到多个对象之间的交互,引入代理模式会增加系统的复杂性,在需要频繁访问真实对象时,还可能会有一些性能问题。
装饰模式
基本概念
通常情况下,扩展类的功能可以通过继承实现,但是扩展越多,子类越多,装饰模式(Decorator Pattern
, 结构型设计模式)可以在**不定义子类的情况下动态的给对象添加一些额外的功能。**具体的做法是将原始对象放入包含行为的特殊封装类(装饰类),从而为原始对象动态添加新的行为,而无需修改其代码。
基本结构:
装饰模式包含以下四个主要角色:
-
组件Component
:通常是抽象类或者接口,是具体组件和装饰者的父类,定义了具体组件需要实现的方法,比如说我们定义Coffee
为组件。
-
具体组件ConcreteComponent
: 实现了Component接口的具体类,是被装饰的对象。
-
装饰类Decorator
: 一个抽象类,给具体组件添加功能,但是具体的功能由其子类具体装饰者完成,持有一个指向Component对象的引用。
-
具体装饰类ConcreteDecorator
: 扩展Decorator类,负责向Component对象添加新的行为,加牛奶的咖啡是一个具体装饰类,加糖的咖啡也是一个具体装饰类。
基本实现
装饰模式的实现包括以下步骤:
-
定义Component接口
class Component { public: virtual ~Component() {} virtual void operation() = 0; };
|
-
实现 ConcreteComponent
class ConcreteComponent : public Component { public: void operation() override { std::cout << "ConcreteComponent operation" << std::endl; } };
|
-
定义Decorator装饰类,继承自Component
class Decorator : public Component { protected: Component* component;
public: Decorator(Component* component) : component(component) {} virtual ~Decorator() { delete component; }
void operation() override { component->operation(); } };
|
-
定义具体的装饰者实现,给具体组件对象添加功能。
class ConcreteDecorator : public Decorator { public: ConcreteDecorator(Component* component) : Decorator(component) {}
void operation() override { std::cout << "Before operation in ConcreteDecorator" << std::endl; Decorator::operation(); std::cout << "After operation in ConcreteDecorator" << std::endl; } };
|
-
在客户端使用
int main() { Component* concreteComponent = new ConcreteComponent();
Component* decorator = new ConcreteDecorator(concreteComponent);
decorator->operation();
delete decorator;
return 0; }
|
应用场景
装饰模式通常在以下几种情况使用:
外观模式
基本概念
外观模式Facade Pattern
, 也被称为“门面模式”,是一种结构型设计模式,外观模式定义了一个高层接口,这个接口使得子系统更容易使用,同时也隐藏了子系统的复杂性。
门面模式可以将子系统关在“门里”隐藏起来,客户端只需要通过外观接口与外观对象进行交互,而不需要直接和多个子系统交互,无论子系统多么复杂,对于外部来说是隐藏的,这样可以降低系统的耦合度。
基本结构
外观模式的基本结构比较简单,只包括“外观”和“子系统类”
简易实现
class SubsystemA { public: void operationA() { std::cout << "SubsystemA operation" << std::endl; } };
class SubsystemB { public: void operationB() { std::cout << "SubsystemB operation" << std::endl; } };
class SubsystemC { public: void operationC() { std::cout << "SubsystemC operation" << std::endl; } };
class Facade { private: SubsystemA* subsystemA; SubsystemB* subsystemB; SubsystemC* subsystemC;
public: Facade() { subsystemA = new SubsystemA(); subsystemB = new SubsystemB(); subsystemC = new SubsystemC(); }
~Facade() { delete subsystemA; delete subsystemB; delete subsystemC; }
void facadeOperation() { subsystemA->operationA(); subsystemB->operationB(); subsystemC->operationC(); } };
int main() { Facade facade;
facade.facadeOperation();
return 0; }
|
在上面的代码中,Facade
类是外观类,封装了对三个子系统SubSystem
的操作。客户端通过调用外观类的方法来实现对子系统的访问,而不需要直接调用子系统的方法。
优缺点和使用场景
外观模式通过提供一个简化的接口,隐藏了系统的复杂性,降低了客户端和子系统之间的耦合度,客户端不需要了解系统的内部实现细节,也不需要直接和多个子系统交互,只需要通过外观接口与外观对象进行交互。
但是如果需要添加新的子系统或修改子系统的行为,就可能需要修改外观类,这违背了“开闭原则”。
桥接模式
基本概念
桥接模式(Bridge Pattern)是一种结构型设计模式,它的UML图很像一座桥,它通过将【抽象部分】与【实现部分】分离,使它们可以独立变化,从而达到降低系统耦合度的目的。桥接模式的主要目的是通过组合建立两个类之间的联系,而不是继承的方式。
基本结构
桥接模式的基本结构分为以下几个角色:
-
抽象Abstraction
:一般是抽象类,定义抽象部分的接口,维护一个对【实现】的引用。
-
修正抽象RefinedAbstaction
:对抽象接口进行扩展,通常对抽象化的不同维度进行变化或定制。
-
实现Implementor
: 定义实现部分的接口,提供具体的实现。这个接口通常是抽象化接口的实现。
-
具体实现ConcreteImplementor
:实现实现化接口的具体类。这些类负责实现实现化接口定义的具体操作。
简易实现
下面是实现桥接模式的基本步骤:
-
定义实现接口
class Implementation { public: virtual ~Implementation() {} virtual void operationImpl() = 0; };
|
-
创建具体实现类
class ConcreteImplementationA : public Implementation { public: void operationImpl() override { std::cout << "ConcreteImplementationA operation" << std::endl; } };
class ConcreteImplementationB : public Implementation { public: void operationImpl() override { std::cout << "ConcreteImplementationB operation" << std::endl; } };
|
-
创建抽象接口
class Abstraction { protected: Implementation* implementation;
public: Abstraction(Implementation* implementation) : implementation(implementation) {} virtual ~Abstraction() { delete implementation; }
virtual void operation() { implementation->operationImpl(); } };
|
-
实现抽象接口,创建 RefinedAbstraction 类
class RefinedAbstraction : public Abstraction { public: RefinedAbstraction(Implementation* implementation) : Abstraction(implementation) {}
void operation() override { implementation->operationImpl(); } };
|
-
客户端代码
int main() { Implementation* implementationA = new ConcreteImplementationA(); Implementation* implementationB = new ConcreteImplementationB();
Abstraction* abstractionA = new RefinedAbstraction(implementationA); Abstraction* abstractionB = new RefinedAbstraction(implementationB);
abstractionA->operation(); abstractionB->operation();
delete abstractionA; delete abstractionB;
return 0; }
|
使用场景
桥接模式在日常开发中使用的并不是特别多,通常在以下情况下使用:
总体而言,桥接模式适用于那些有多个独立变化维度、需要灵活扩展的系统。
组合模式
基本概念
组合模式是一种结构型设计模式,它将对象组合成树状结构来表示“部分-整体”的层次关系。组合模式使得客户端可以统一处理单个对象和对象的组合,而无需区分它们的具体类型。
基本结构
组合模式包括下面几个角色:
Component
组件: 组合模式的“根节点”,定义组合中所有对象的通用接口,可以是抽象类或接口。该类中定义了子类的共性内容。
通过组合模式,整个省份的获取信息操作可以一次性地执行,而无需关心省份中的具体城市。这样就实现了对国家省份和城市的管理和操作。
简易实现
class Component { public: virtual ~Component() {} virtual void operation() = 0; };
class Leaf : public Component { public: void operation() override { std::cout << "Leaf operation" << std::endl; } };
class Composite : public Component { private: std::vector<Component*> components;
public: ~Composite() { for (Component* component : components) { delete component; } }
void add(Component* component) { components.push_back(component); }
void remove(Component* component) { components.erase(std::remove(components.begin(), components.end(), component), components.end()); }
void operation() override { std::cout << "Composite operation" << std::endl; for (Component* component : components) { component->operation(); } } };
int main() { Component* leaf = new Leaf();
Composite* composite = new Composite(); composite->add(leaf);
composite->operation();
delete composite;
return 0; }
|
使用场景
组合模式可以使得客户端可以统一处理单个对象和组合对象,无需区分它们之间的差异,比如在图形编辑器中,图形对象可以是简单的线、圆形,也可以是复杂的组合图形,这个时候可以对组合节点添加统一的操作。
总的来说,组合模式适用于任何需要构建具有部分-整体层次结构的场景,比如组织架构管理、文件系统的文件和文件夹组织等。
享元模式
基础概念
享元模式是一种结构型设计模式,在享元模式中,对象被设计为可共享的,可以被多个上下文使用,而不必在每个上下文中都创建新的对象。想要了解享元模式,就必须要区分什么是内部状态,什么是外部状态。
基本结构
享元模式包括以下几个重要角色:
-
享元接口Flyweight
: 所有具体享元类的共享接口,通常包含对外部状态的操作。
-
具体享元类ConcreteFlyweight
: 继承Flyweight
类或实现享元接口,包含内部状态。
-
享元工厂类FlyweightFactory
: 创建并管理享元对象,当用户请求时,提供已创建的实例或者创建一个。
-
客户端Client
: 维护外部状态,在使用享元对象时,将外部状态传递给享元对象。
简易实现
享元模式的实现通常涉及以下步骤:
-
定义享元接口,接受外部状态作为参数并进行处理。
class Flyweight { public: virtual ~Flyweight() {} virtual void operation(const std::string& externalState) = 0; };
|
-
实现具体享元类, 存储内部状态。
class ConcreteFlyweight : public Flyweight { private: std::string intrinsicState;
public: ConcreteFlyweight(const std::string& intrinsicState) : intrinsicState(intrinsicState) {}
void operation(const std::string& externalState) override { std::cout << "Intrinsic State: " << intrinsicState << ", External State: " << externalState << std::endl; } };
|
-
创建享元工厂类,创建并管理Flyweight
对象,当用户请求一个Flyweight
时,享元工厂会提供一个已经创建的实例或者创建一个。
class FlyweightFactory { private: std::unordered_map<std::string, Flyweight*> flyweights;
public: ~FlyweightFactory() { for (auto& pair : flyweights) { delete pair.second; } }
Flyweight* getFlyweight(const std::string& key) { if (flyweights.find(key) == flyweights.end()) { flyweights[key] = new ConcreteFlyweight(key); } return flyweights[key]; } };
|
-
客户端使用享元模式
int main() { FlyweightFactory factory;
Flyweight* flyweight1 = factory.getFlyweight("A"); flyweight1->operation("External State 1");
Flyweight* flyweight2 = factory.getFlyweight("B"); flyweight2->operation("External State 2");
Flyweight* flyweight3 = factory.getFlyweight("A"); flyweight3->operation("External State 3");
return 0; }
|
使用场景
使用享元模式的关键在于包含大量相似对象,并且这些对象的内部状态可以共享。具体的应用场景包括文本编辑器,图形编辑器,游戏中的角色创建,这些对象的内部状态比较固定(外观,技能,形状),但是外部状态变化比较大时,可以使用。
行为型模式
观察者模式
观察者模式(发布-订阅模式)属于行为型模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听一个主题对象,当主题对象的状态发生变化时,所有依赖于它的观察者都得到通知并被自动更新。
基本概念
观察者模式依赖两个模块:
使用观察者模式有很多好处,比如说观察者模式将主题和观察者之间的关系解耦,主题只需要关注自己的状态变化,而观察者只需要关注在主题状态变化时需要执行的操作,两者互不干扰,并且由于观察者和主题是相互独立的,可以轻松的增加和删除观察者,这样实现的系统更容易扩展和维护。
基本结构
观察者模式依赖主题和观察者,但是一般有4个组成部分:
-
主题Subject
, 一般会定义成一个接口,提供方法用于注册、删除和通知观察者,通常也包含一个状态,当状态发生改变时,通知所有的观察者。
-
观察者Observer
: 观察者也需要实现一个接口,包含一个更新方法,在接收主题通知时执行对应的操作。
-
具体主题ConcreteSubject
: 主题的具体实现, 维护一个观察者列表,包含了观察者的注册、删除和通知方法。
-
具体观察者ConcreteObserver
: 观察者接口的具体实现,每个具体观察者都注册到具体主题中,当主题状态变化并通知到具体观察者,具体观察者进行处理。
基本实现
根据上面的类图,我们可以写出观察者模式的基本实现
class Observer;
class Subject { public: virtual ~Subject() {} virtual void registerObserver(Observer* observer) = 0; virtual void removeObserver(Observer* observer) = 0; virtual void notifyObservers() = 0; };
class Observer { public: virtual ~Observer() {} virtual void update(const std::string& message) = 0; };
class ConcreteSubject : public Subject { private: std::vector<Observer*> observers; std::string state;
public: void registerObserver(Observer* observer) override { observers.push_back(observer); }
void removeObserver(Observer* observer) override { observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end()); }
void notifyObservers() override { for (Observer* observer : observers) { observer->update(state); } }
void setState(const std::string& newState) { state = newState; notifyObservers(); } };
class ConcreteObserver : public Observer { public: void update(const std::string& message) override { std::cout << "Observer received message: " << message << std::endl; } };
int main() { ConcreteSubject subject;
ConcreteObserver observer1; ConcreteObserver observer2;
subject.registerObserver(&observer1); subject.registerObserver(&observer2);
subject.setState("State 1"); subject.setState("State 2");
subject.removeObserver(&observer1); subject.setState("State 3");
return 0; }
|
使用场景
观察者模式特别适用于一个对象的状态变化会影响到其他对象,并且希望这些对象在状态变化时能够自动更新的情况。 比如说在图形用户界面中,按钮、滑动条等组件的状态变化可能需要通知其他组件更新,这使得观察者模式被广泛应用于GUI框架,比如Java的Swing框架。
此外,观察者模式在前端开发和分布式系统中也有应用,比较典型的例子是前端框架Vue
, 当数据发生变化时,视图会自动更新。而在分布式系统中,观察者模式可以用于实现节点之间的消息通知机制,节点的状态变化将通知其他相关节点。
策略模式
基本概念
策略模式是一种行为型设计模式,它定义了一系列算法(这些算法完成的是相同的工作,只是实现不同),并将每个算法封装起来,使它们可以相互替换,而且算法的变化不会影响使用算法的客户。
基本结构
策略模式包含下面几个结构:
-
策略类Strategy
: 定义所有支持的算法的公共接口。
-
具体策略类ConcreteStrategy
: 实现了策略接口,提供具体的算法实现。
-
上下文类Context
: 包含一个策略实例,并在需要时调用策略对象的方法。
简单实现
下面是一个简单的策略模式的基本实现:
class Strategy { public: virtual ~Strategy() {} virtual void algorithmInterface() const = 0; };
class ConcreteStrategyA : public Strategy { public: void algorithmInterface() const override { std::cout << "Strategy A" << std::endl; } };
class ConcreteStrategyB : public Strategy { public: void algorithmInterface() const override { std::cout << "Strategy B" << std::endl; } };
class Context { private: Strategy* strategy;
public: Context(Strategy* strategy) : strategy(strategy) {}
void contextInterface() const { strategy->algorithmInterface(); }
void setStrategy(Strategy* strategy) { this->strategy = strategy; } };
int main() { Context contextA(new ConcreteStrategyA()); contextA.contextInterface();
Context contextB(new ConcreteStrategyB()); contextB.contextInterface();
return 0; }
|
使用场景
那什么时候可以考虑使用策略模式呢?
命令模式
基本概念
命令模式是一种行为型设计模式,其允许将请求封装成一个对象(命令对象,包含执行操作所需的所有信息),并将命令对象按照一定的顺序存储在队列中,然后再逐一调用执行,这些命令也可以支持反向操作,进行撤销和重做。
这样一来,发送者只需要触发命令就可以完成操作,不需要知道接受者的具体操作,从而实现两者间的解耦。
基本结构
命令模式包含以下几个基本角色:
-
命令接口Command
:接口或者抽象类,定义执行操作的接口。
-
具体命令类ConcreteCommand
: 实现命令接口,执行具体操作,在调用execute
方法时使“接收者对象”根据命令完成具体的任务
-
接收者类Receiver
: 接受并执行命令的对象,可以是任何对象
-
调用者类Invoker
: 发起请求的对象,有一个将命令作为参数传递的方法。它不关心命令的具体实现,只负责调用命令对象的 execute()
方法来传递请求
-
客户端:创建具体的命令对象和接收者对象,然后将它们组装起来。
简易实现
-
定义执行操作的接口:包含一个execute
方法。有的时候还会包括unExecute
方法,表示撤销命令。
class Command { public: virtual ~Command() {} virtual void execute() = 0; virtual void undo() = 0; };
|
-
实现命令接口,执行具体的操作。
class ConcreteCommand : public Command { private: Receiver* receiver;
public: ConcreteCommand(Receiver* receiver) : receiver(receiver) {}
void execute() override { receiver->action(); }
void undo() override { receiver->undoAction(); } };
|
-
定义接受者类,知道如何实施与执行一个请求相关的操作。
class Receiver { public: void action() { std::cout << "Action executed." << std::endl; }
void undoAction() { std::cout << "Action undone." << std::endl; } };
|
-
定义调用者类,调用命令对象执行请求。
调用者类中可以维护一个命令队列或者“撤销栈”,以支持批处理和撤销命令。
class Invoker { private: std::queue<std::shared_ptr<Command>> commandQueue; std::stack<std::shared_ptr<Command>> undoStack;
public: void setAndExecuteCommand(std::shared_ptr<Command> command) { command->execute(); commandQueue.push(command); undoStack.push(command); }
void undoLastCommand() { if (!undoStack.empty()) { auto lastCommand = undoStack.top(); undoStack.pop(); lastCommand->undo(); removeCommandFromQueue(lastCommand); } else { std::cout << "No command to undo." << std::endl; } }
void executeCommandsInQueue() { std::queue<Command*> tempQueue = commandQueue; while (!tempQueue.empty()) { Command* command = tempQueue.front(); command->execute(); tempQueue.pop(); } } private: void removeCommandFromQueue(Command* command) { std::queue<Command*> tempQueue; while (!commandQueue.empty()) { Command* currentCommand = commandQueue.front(); commandQueue.pop(); if (currentCommand != command) { tempQueue.push(currentCommand); } } commandQueue = tempQueue; } };
|
-
客户端使用,创建具体的命令对象和接收者对象,然后进行组装。
int main() { auto receiver = std::make_shared<Receiver>(); auto command = std::make_shared<ConcreteCommand>(receiver.get()); Invoker invoker; invoker.setAndExecuteCommand(command); invoker.undoLastCommand(); invoker.executeCommandsInQueue();
return 0; }
|
优缺点和使用场景
命令模式在需要将请求封装成对象、支持撤销和重做、设计命令队列等情况下,都是一个有效的设计模式。
-
撤销操作: 需要支持撤销操作,命令模式可以存储历史命令,轻松实现撤销功能。
-
队列请求: 命令模式可以将请求排队,形成一个命令队列,依次执行命令。
-
可扩展性: 可以很容易地添加新的命令类和接收者类,而不影响现有的代码。新增命令不需要修改现有代码,符合开闭原则。
但是对于每个命令,都会有一个具体命令类,这可能导致类的数量急剧增加,增加了系统的复杂性。
中介者模式
基本概念
中介者模式(Mediator Pattern)也被称为调停者模式,是一种行为型设计模式,它通过一个中介对象来封装一组对象之间的交互,从而使这些对象不需要直接相互引用。这样可以降低对象之间的耦合度,使系统更容易维护和扩展。
基本结构
中介者模式包括以下几个重要角色:
-
抽象中介者(Mediator): 定义中介者的接口,用于各个具体同事对象之间的通信。
-
具体中介者(Concrete Mediator): 实现抽象中介者接口,负责协调各个具体同事对象的交互关系,它需要知道所有具体同事类,并从具体同事接收消息,向具体同事对象发出命令。
-
抽象同事类(Colleague): 定义同事类的接口,维护一个对中介者对象的引用,用于通信。
-
具体同事类(Concrete Colleague): 实现抽象同事类接口,每个具体同事类只知道自己的行为,而不了解其他同事类的情况,因为它们都需要与中介者通信,通过中介者协调与其他同事对象的交互。
简易实现
class Colleague;
class Mediator { public: virtual void registerColleague(Colleague* colleague) = 0; virtual void send(std::string message, Colleague* colleague) = 0; };
class ConcreteMediator : public Mediator { private: std::vector<Colleague*> colleagues;
public: void registerColleague(Colleague* colleague) override { colleagues.push_back(colleague); }
void send(std::string message, Colleague* colleague) override { for (Colleague* c : colleagues) { if (c != colleague) { c->receive(message); } } } };
class Colleague { protected: Mediator* mediator;
public: Colleague(Mediator* mediator) : mediator(mediator) {}
virtual void send(std::string message) = 0; virtual void receive(std::string message) = 0; };
class ConcreteColleague1 : public Colleague { public: ConcreteColleague1(Mediator* mediator) : Colleague(mediator) {}
void send(std::string message) override { mediator->send(message, this); }
void receive(std::string message) override { std::cout << "ConcreteColleague1 received: " << message << std::endl; } };
class ConcreteColleague2 : public Colleague { public: ConcreteColleague2(Mediator* mediator) : Colleague(mediator) {}
void send(std::string message) override { mediator->send(message, this); }
void receive(std::string message) override { std::cout << "ConcreteColleague2 received: " << message << std::endl; } };
int main() { Mediator* mediator = new ConcreteMediator();
Colleague* colleague1 = new ConcreteColleague1(mediator); Colleague* colleague2 = new ConcreteColleague2(mediator);
mediator->registerColleague(colleague1); mediator->registerColleague(colleague2);
colleague1->send("Hello from Colleague1!"); colleague2->send("Hi from Colleague2!");
delete colleague1; delete colleague2; delete mediator;
return 0; }
|
使用场景
中介者模式使得同事对象不需要知道彼此的细节,只需要与中介者进行通信,简化了系统的复杂度,也降低了各对象之间的耦合度,但是这也会使得中介者对象变得过于庞大和复杂,如果中介者对象出现问题,整个系统可能会受到影响。
中介者模式适用于当系统对象之间存在复杂的交互关系或者系统需要在不同对象之间进行灵活的通信时使用,可以使得问题简化,
备忘录模式
基本概念
备忘录模式(Memento Pattern)是一种行为型设计模式,它允许在不暴露对象实现的情况下捕获对象的内部状态并在对象之外保存这个状态,以便稍后可以将其还原到先前的状态。
基本结构
备忘录模式包括以下几个重要角色:
备忘录有两个接口,发起人能够通过宽接口访问数据,管理者只能看到窄接口,并将备忘录传递给其他对象。
基本实现
-
创建发起人类:可以创建备忘录对象
class Memento;
class Originator { private: std::string state;
public: void setState(const std::string& state) { this->state = state; }
std::string getState() const { return state; }
Memento createMemento();
void restoreFromMemento(const Memento& memento); };
Memento Originator::createMemento() { return Memento(state); }
void Originator::restoreFromMemento(const Memento& memento) { state = memento.getState(); }
|
-
创建备忘录类:保存发起人对象的状态
class Memento { private: std::string state;
friend class Originator;
Memento(const std::string& state) : state(state) {}
public: std::string getState() const { return state; } };
|
-
创建管理者:维护一组备忘录对象
class Caretaker { private: std::vector<Memento> mementos;
public: void addMemento(const Memento& memento) { mementos.push_back(memento); }
Memento getMemento(size_t index) const { if (index < mementos.size()) { return mementos[index]; } throw std::out_of_range("Invalid memento index"); } };
|
-
客户端使用备忘录模式
int main() { Originator originator; originator.setState("State 1");
Caretaker caretaker;
caretaker.addMemento(originator.createMemento());
originator.setState("State 2");
caretaker.addMemento(originator.createMemento());
originator.restoreFromMemento(caretaker.getMemento(0));
std::cout << "Current State: " << originator.getState() << std::endl;
return 0; }
|
使用场景
备忘录模式在保证了对象内部状态的封装和私有性前提下可以轻松地添加新的备忘录和发起人,实现“备份”,不过 备份对象往往会消耗较多的内存,资源消耗增加。
模板方法模式
基本概念
模板方法模式(Template Method Pattern)是一种行为型设计模式, 它定义了一个算法的骨架,将**一些步骤的实现延迟到子类。**模板方法模式使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。【引用自大话设计第10章】
基本结构
模板方法模式的基本结构包含以下两个角色:
简易实现
模板方法模式的简单示例如下:
-
定义模板类,包含模板方法,定义了算法的骨架, 一般都加上final
关键字,避免子类重写。
template<typename T> class AbstractClass { public: void templateMethod() { step1(); step2(); step3(); }
protected: virtual void step1() = 0; virtual void step2() = 0; virtual void step3() = 0; };
|
-
定义具体类, 实现模板类中的抽象方法
class ConcreteClass : public AbstractClass<ConcreteClass> { protected: void step1() override { std::cout << "Step 1" << std::endl; }
void step2() override { std::cout << "Step 2" << std::endl; }
void step3() override { std::cout << "Step 3" << std::endl; } };
|
-
客户端实现
int main() { ConcreteClass concreteTemplate; concreteTemplate.templateMethod(); return 0; }
|
应用场景
模板方法模式将算法的不变部分被封装在模板方法中,而可变部分算法由子类继承实现,这样做可以很好的提高代码的复用性,但是当算法的框架发生变化时,可能需要修改模板类,这也会影响到所有的子类。
迭代器模式
基本概念
迭代器模式是一种行为设计模式,是一种使用频率非常高的设计模式,在各个语言中都有应用,其主要目的是**提供一种统一的方式来访问一个聚合对象中的各个元素,**而不需要暴露该对象的内部表示。通过迭代器,客户端可以顺序访问聚合对象的元素,而无需了解底层数据结构。
迭代器模式应用广泛,但是大多数语言都已经内置了迭代器接口,不需要自己实现。
基本结构
迭代器模式包括以下几个重要角色
-
迭代器接口Iterator
:定义访问和遍历元素的接口, 通常会包括hasNext()
方法用于检查是否还有下一个元素,以及next()
方法用于获取下一个元素。有的还会实现获取第一个元素以及获取当前元素的方法。
-
具体迭代器ConcreateIterator
:实现迭代器接口,实现遍历逻辑对聚合对象进行遍历。
-
抽象聚合类:定义了创建迭代器的接口,包括一个createIterator
方法用于创建一个迭代器对象。
-
具体聚合类:实现在抽象聚合类中声明的createIterator()
方法,返回一个与具体聚合对应的具体迭代器
简易实现
-
定义迭代器接口:通常会有检查是否还有下一个元素以及获取下一个元素的方法。
class Iterator { public: virtual ~Iterator() = default;
virtual bool hasNext() const = 0;
virtual void* next() = 0; };
|
-
定义具体迭代器:实现迭代器接口,遍历集合。
class ConcreteIterator : public Iterator { private: size_t index; std::vector<void*> elements;
public: ConcreteIterator(const std::vector<void*>& elements) : elements(elements), index(0) {}
bool hasNext() const override { return index < elements.size(); }
void* next() override { if (hasNext()) { return elements[index++]; } return nullptr; } };
|
-
定义聚合接口:通常包括createIterator()
方法,用于创建迭代器
class Iterable { public: virtual ~Iterable() = default;
virtual Iterator* createIterator() const = 0; };
|
-
实现具体聚合:创建具体的迭代器
class ConcreteIterable : public Iterable { private: std::vector<void*> elements;
public: ConcreteIterable(const std::vector<void*>& elements) : elements(elements) {}
Iterator* createIterator() const override { return new ConcreteIterator(elements); } };
|
-
客户端使用
int main() { std::vector<void*> elements; elements.push_back((void*)"Element 1"); elements.push_back((void*)"Element 2"); elements.push_back((void*)"Element 3");
Iterable* iterable = new ConcreteIterable(elements); Iterator* iterator = iterable->createIterator();
while (iterator->hasNext()) { std::cout << static_cast<char*>(iterator->next()) << std::endl; }
delete iterator; delete iterable;
return 0; }
|
使用场景
迭代器模式是一种通用的设计模式,其封装性强,简化了客户端代码,客户端不需要知道集合的内部结构,只需要关心迭代器和迭代接口就可以完成元素的访问。但是引入迭代器模式会增加额外的类,每增加一个集合类,都需要增加该集合对应的迭代器,这也会使得代码结构变得更加复杂。
状态模式
基本结构
状态模式(State Pattern)是一种行为型设计模式,它适用于一个对象在在不同的状态下有不同的行为时,比如说电灯的开、关、闪烁是不停的状态,状态不同时,对应的行为也不同,在没有状态模式的情况下,为了添加新的状态或修改现有的状态,往往需要修改已有的代码,这违背了开闭原则,而且如果对象的状态切换逻辑和各个状态的行为都在同一个类中实现,就可能导致该类的职责过重,不符合单一职责原则。
而状态模式将每个状态的行为封装在一个具体状态类中,使得每个状态类相对独立,并将对象在不同状态下的行为进行委托,从而使得对象的状态可以在运行时动态改变,每个状态的实现也不会影响其他状态。
基本结构:
状态模式包括以下几个重要角色:
-
State
(状态): 定义一个接口,用于封装与Context的一个特定状态相关的行为。
-
ConcreteState
(具体状态): 负责处理Context在状态改变时的行为, 每一个具体状态子类实现一个与Context
的一个状态相关的行为。
-
Context
(上下文): 维护一个具体状态子类的实例,这个实例定义当前的状态。
基本使用、
-
定义状态接口:创建一个状态接口,该接口声明了对象可能的各种状态对应的方法。
class State { public: virtual ~State() = default; virtual void handle() = 0; };
|
-
实现具体状态类: 为对象可能的每种状态创建具体的状态类,实现状态接口中定义的方法。
class ConcreteState1 : public State { public: void handle() override { std::cout << "Handling state 1" << std::endl; } };
class ConcreteState2 : public State { public: void handle() override { std::cout << "Handling state 2" << std::endl; } };
|
-
创建上下文类:该类包含对状态的引用,并在需要时调用当前状态的方法。
class Context { private: std::shared_ptr<State> currentState;
public: void setState(const std::shared_ptr<State>& state) { this->currentState = state; }
void request() { if (currentState) { currentState->handle(); } else { std::cerr << "State is not set!" << std::endl; } } };
|
-
客户端使用:创建具体的状态对象和上下文对象,并通过上下文对象调用相应的方法。通过改变状态,可以改变上下文对象的行为
int main() { Context context;
std::shared_ptr<State> state1 = std::make_shared<ConcreteState1>(); std::shared_ptr<State> state2 = std::make_shared<ConcreteState2>();
context.setState(state1); context.request();
context.setState(state2); context.request();
return 0; }
|
使用场景
状态模式将每个状态的实现都封装在一个类中,每个状态类的实现相对独立,使得添加新状态或修改现有状态变得更加容易,避免了使用大量的条件语句来控制对象的行为。但是如果状态过多,会导致类的数量增加,可能会使得代码结构复杂。
责任链模式
基本概念
责任链模式是一种行为型设计模式,它允许你构建一个对象链,让请求从链的一端进入,然后沿着链上的对象依次处理,直到链上的某个对象能够处理该请求为止。
职责链上的处理者就是一个对象,可以对请求进行处理或者将请求转发给下一个节点,这个场景在生活中很常见,就是一个逐层向上递交的过程,最终的请求要么被处理者所处理,要么处理不了,这也因此可能导致请求无法被处理。
组成结构
责任链模式包括以下几个基本结构:
-
处理者Handler
:定义一个处理请求的接口,包含一个处理请求的抽象方法和一个指向下一个处理者的链接。
-
具体处理者ConcreteHandler
: 实现处理请求的方法,并判断能否处理请求,如果能够处理请求则进行处理,否则将请求传递给下一个处理者。
-
客户端:创建并组装处理者对象链,并将请求发送到链上的第一个处理者。
简易实现
-
处理者:定义处理请求的接口
class Handler { public: virtual void handleRequest(double amount) = 0; virtual void setNextHandler(Handler* nextHandler) = 0; virtual ~Handler() {} };
|
-
具体处理者:实现处理请求
class ConcreteHandler : public Handler { private: Handler* nextHandler;
public: ConcreteHandler() : nextHandler(nullptr) {}
void handleRequest(double amount) override { if (canHandle(amount)) { std::cout << "Handled by ConcreteHandler" << std::endl; } else if (nextHandler != nullptr) { nextHandler->handleRequest(amount); } else { std::cout << "Cannot be handled" << std::endl; } }
void setNextHandler(Handler* nextHandler) override { this->nextHandler = nextHandler; }
private: bool canHandle(double amount) { return ; } };
|
-
客户端创建并组装处理者对象链,将请求发送给链上第一个处理者
int main() { Handler* handler1 = new ConcreteHandler(); Handler* handler2 = new ConcreteHandler();
handler1->setNextHandler(handler2);
double requestAmount = 100.0; handler1->handleRequest(requestAmount);
delete handler1; delete handler2;
return 0; }
|
使用场景
责任链模式具有下面几个优点:
但是由于一个请求可能会经过多个处理者,这可能会导致一些性能问题,并且如果整个链上也没有合适的处理者来处理请求,就会导致请求无法被处理。
解释器模式
基本概念
解释器模式(Interpreter Pattern)是一种行为型设计模式,它定义了一个语言的文法,并且建立一个【解释器】来解释该语言中的句子。
组成结构
解释器模式主要包含以下几个角色:
-
抽象表达式(Abstract Expression): 定义了解释器的接口,包含了解释器的方法 interpret
。
-
终结符表达式(Terminal Expression): 在语法中不能再分解为更小单元的符号。
-
非终结符表达式(Non-terminal Expression): 文法中的复杂表达式,它由终结符和其他非终结符组成。
-
上下文(Context): 包含解释器之外的一些全局信息,可以存储解释器中间结果,也可以用于向解释器传递信息。
举例来说,表达式 “3 + 5 * 2”,数字 “3” 和 “5”, “2” 是终结符,而运算符 “+”, "*"都需要两个操作数, 属于非终结符。
简易实现
-
创建抽象表达式接口: 定义解释器的接口,声明一个 interpret
方法,用于解释语言中的表达式。
class Expression { public: virtual int interpret() = 0; virtual ~Expression() {} };
|
-
创建具体的表达式类: 实现抽象表达式接口,用于表示语言中的具体表达式。
class TerminalExpression : public Expression { private: int value;
public: TerminalExpression(int value) : value(value) {}
int interpret() override { return value; } };
|
-
非终结符表达式:抽象表达式的一种,用于表示语言中的非终结符表达式,通常包含其他表达式。
class AddExpression : public Expression { private: Expression* left; Expression* right;
public: AddExpression(Expression* left, Expression* right) : left(left), right(right) {}
int interpret() override { return left->interpret() + right->interpret(); } };
|
-
上下文:包含解释器需要的一些全局信息或状态。
-
客户端:构建并组合表达式,然后解释表达式。
int main() { Expression* expression = new AddExpression( new TerminalExpression(1), new TerminalExpression(2) );
int result = expression->interpret(); std::cout << "Result: " << result << std::endl;
delete expression;
return 0; }
|
使用场景
当需要解释和执行特定领域或业务规则的语言时,可以使用解释器模式。例如,SQL解释器、正则表达式解释器等。但是需要注意的是解释器模式可能会导致类的层次结构较为复杂,同时也可能不够灵活,使用要慎重。
访问者模式
基本概念
访问者模式(Visitor Pattern)是一种行为型设计模式,可以在不改变对象结构的前提下,对对象中的元素进行新的操作。
基本结构:
访问者模式包括以下几个基本角色:
-
抽象访问者(Visitor): 声明了访问者可以访问哪些元素,以及如何访问它们的方法visit
。
-
具体访问者(ConcreteVisitor): 实现了抽象访问者定义的方法,不同的元素类型可能有不同的访问行为。医生、管理员、游客都属于具体的访问者,它们的访问行为不同。
-
抽象元素(Element): 定义了一个accept方法,用于接受访问者的访问。
-
具体元素(ConcreteElement): 实现了accept方法,是访问者访问的目标。
-
对象结构(Object Structure): 包含元素的集合,可以是一个列表、一个集合或者其他数据结构。负责遍历元素,并调用元素的接受方法。
简易实现:
-
定义抽象访问者: 声明那些元素可以访问
class ConcreteElementA; class ConcreteElementB;
class Visitor { public: virtual void visit(ConcreteElementA& element) = 0; virtual void visit(ConcreteElementB& element) = 0; virtual ~Visitor() {} };
|
-
实现具体访问者:实现具体的访问逻辑
class ConcreteVisitorA : public Visitor { public: void visit(ConcreteElementA& element) override { std::cout << "ConcreteVisitorA Visit ConcreteElementA" << std::endl; }
void visit(ConcreteElementB& element) override { std::cout << "ConcreteVisitorA Visit ConcreteElementB" << std::endl; } };
class ConcreteVisitorB : public Visitor { public: void visit(ConcreteElementA& element) override { std::cout << "ConcreteVisitorB Visit ConcreteElementA" << std::endl; }
void visit(ConcreteElementB& element) override { std::cout << "ConcreteVisitorB Visit ConcreteElementB" << std::endl; } };
|
-
定义元素接口:声明接收访问者的方法。
class Visitor;
class Element { public: virtual void accept(Visitor& visitor) = 0; virtual ~Element() {} };
|
-
实现具体元素:实现接受访问者的方法
class ConcreteElementA : public Element { public: void accept(Visitor& visitor) override { visitor.visit(*this); } };
class ConcreteElementB : public Element { public: void accept(Visitor& visitor) override { visitor.visit(*this); } };
|
-
创建对象结构:提供一个接口让访问者访问它的元素。
class ObjectStructure { private: std::vector<Element*> elements;
public: void attach(Element* element) { elements.push_back(element); }
void detach(Element* element) { }
void accept(Visitor& visitor) { for (Element* element : elements) { element->accept(visitor); } } };
|
-
客户端调用
int main() { ObjectStructure objectStructure; objectStructure.attach(new ConcreteElementA()); objectStructure.attach(new ConcreteElementB());
ConcreteVisitorA visitorA; ConcreteVisitorB visitorB;
objectStructure.accept(visitorA); objectStructure.accept(visitorB);
for (Element* element : objectStructure.elements) { delete element; }
return 0; }
|
使用场景
访问者模式结构较为复杂,但是访问者模式将同一类操作封装在一个访问者中,使得相关的操作彼此集中,提高了代码的可读性和维护性。它常用于对象结构比较稳定,但经常需要在此对象结构上定义新的操作,这样就无需修改现有的元素类,只需要定义新的访问者来添加新的操作。
Reference
[1] C++ 深入浅出工厂模式(初识篇): https://zhuanlan.zhihu.com/p/83535678
[2] 卡码网设计模式精讲:https://github.com/youngyangyang04/kama-DesignPattern?tab=readme-ov-file