目录
6.2 Curiously Recurring Template Pattern (CRTP)
引言
设计模式是软件工程中解决特定问题的通用解决方案。它们并不是代码片段,而是对问题和解决方案的描述,旨在帮助开发者编写更易维护、更具扩展性的代码。本篇文章将深入讲解几种经典和现代设计模式,涵盖其概念、模型、特点、核心点、实现、适用场景,以及经典示例代码和详细解析。
1. 单例模式(Singleton)
概念
单例模式确保一个类只有一个实例,并提供一个全局访问点。
模型
- 私有构造函数:防止通过new关键字创建对象。
- 静态私有实例指针:指向唯一的单例实例。
- 公有静态方法:用于提供全局访问点,创建或返回实例。
特点
- 唯一实例:全局唯一的实例,节省资源。
- 懒汉式/饿汉式:实例的创建时机不同。
核心点
- 确保类不能通过构造函数创建多实例。
- 提供一个静态方法访问实例。
实现
class Singleton
{
private:
static Singleton* instance;
// 私有构造函数
Singleton() {}
public:
// 禁用拷贝构造和赋值操作
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton* getInstance()
{
if (instance == nullptr)
{
instance = new Singleton();
}
return instance;
}
};
// 初始化静态成员
Singleton* Singleton::instance = nullptr;
适用场景
- 需要控制系统或模块中的全局唯一实例,如日志记录器、配置管理器等。
经典示例实现
#include <iostream>
class Logger
{
private:
static Logger* instance;
Logger() {}
public:
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
static Logger* getInstance()
{
if (instance == nullptr)
{
instance = new Logger();
}
return instance;
}
void log(const std::string& message)
{
std::cout << "Log: " << message << std::endl;
}
};
Logger* Logger::instance = nullptr;
int main()
{
Logger::getInstance()->log("Singleton Pattern Example");
return 0;
}
代码解析
- 私有构造函数:防止通过new创建对象。
- 静态公有方法
getInstance
:提供全局访问点,懒汉式创建实例。 log
方法:示例业务方法。
2. 工厂模式(Factory)
概念
工厂模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。
模型
- 工厂接口/抽象类:定义创建对象的方法。
- 具体工厂类:实现创建对象的方法。
特点
- 解耦:客户端代码与具体类解耦。
- 扩展性:增加新产品时,只需添加新的具体工厂和产品类。
核心点
- 提供一个创建对象的接口。
- 将对象的创建延迟到子类。
实现
// 抽象产品类,定义了产品的接口
class Product
{
public:
// 纯虚函数,要求子类必须实现
virtual void use() = 0;
};
// 具体产品类A,实现了Product接口
class ConcreteProductA : public Product
{
public:
// 实现use方法
void use() override { std::cout << "Using ConcreteProductA" << std::endl; }
};
// 具体产品类B,实现了Product接口
class ConcreteProductB : public Product
{
public:
// 实现use方法
void use() override { std::cout << "Using ConcreteProductB" << std::endl; }
};
// 抽象工厂类,定义了创建产品的方法
class Factory
{
public:
// 纯虚函数,要求子类必须实现
virtual Product* createProduct() = 0;
};
// 具体工厂类A,实现了Factory接口,负责创建ConcreteProductA对象
class ConcreteFactoryA : public Factory
{
public:
// 实现createProduct方法,返回ConcreteProductA对象
Product* createProduct() override { return new ConcreteProductA(); }
};
// 具体工厂类B,实现了Factory接口,负责创建ConcreteProductB对象
class ConcreteFactoryB : public Factory
{
public:
// 实现createProduct方法,返回ConcreteProductB对象
Product* createProduct() override { return new ConcreteProductB(); }
};
代码解析
抽象产品类
定义了一个纯虚函数Product
use()
,要求所有继承自Product
的具体产品类必须实现该方法。具体产品类
ConcreteProductA
和ConcreteProductB
ConcreteProductA
和ConcreteProductB
分别继承自Product
类,并实现了use()
方法。抽象工厂类
定义了一个纯虚函数Factory
createProduct()
,要求所有继承自Factory
的具体工厂类必须实现该方法。具体工厂类
ConcreteFactoryA
和ConcreteFactoryB
ConcreteFactoryA
和ConcreteFactoryB
分别继承自Factory
类,并实现了createProduct()
方法,分别创建ConcreteProductA
和ConcreteProductB
的对象。
适用场景
- 客户端不需要知道具体产品类的创建逻辑。
- 需要提供灵活的产品创建机制。
经典示例实现
#include <iostream>
// 抽象产品类,定义了产品的接口
class Product
{
public:
// 纯虚函数,要求子类必须实现
virtual void use() = 0;
};
// 具体产品类A,实现了Product接口
class ConcreteProductA : public Product
{
public:
// 实现use方法
void use() override { std::cout << "Using ConcreteProductA" << std::endl; }
};
// 具体产品类B,实现了Product接口
class ConcreteProductB : public Product
{
public:
// 实现use方法
void use() override { std::cout << "Using ConcreteProductB" << std::endl; }
};
// 抽象工厂类,定义了创建产品的方法
class Factory
{
public:
// 纯虚函数,要求子类必须实现
virtual Product* createProduct() = 0;
};
// 具体工厂类A,实现了Factory接口,负责创建ConcreteProductA对象
class ConcreteFactoryA : public Factory
{
public:
// 实现createProduct方法,返回ConcreteProductA对象
Product* createProduct() override { return new ConcreteProductA(); }
};
// 具体工厂类B,实现了Factory接口,负责创建ConcreteProductB对象
class ConcreteFactoryB : public Factory
{
public:
// 实现createProduct方法,返回ConcreteProductB对象
Product* createProduct() override { return new ConcreteProductB(); }
};
int main()
{
// 创建具体工厂A
Factory* factoryA = new ConcreteFactoryA();
// 使用工厂A创建产品A
Product* productA = factoryA->createProduct();
// 使用产品A
productA->use();
// 创建具体工厂B
Factory* factoryB = new ConcreteFactoryB();
// 使用工厂B创建产品B
Product* productB = factoryB->createProduct();
// 使用产品B
productB->use();
// 释放内存
delete productA;
delete productB;
delete factoryA;
delete factoryB;
return 0;
}
代码解析
Product
类:定义产品的接口。ConcreteProductA
和ConcreteProductB
类:实现具体产品。Factory
类:定义工厂接口。ConcreteFactoryA
和ConcreteFactoryB
类:实现具体工厂。
3. 观察者模式(Observer)
概念
观察者模式定义对象间的一种一对多依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并被自动更新。
模型
- 主题接口/抽象类:提供添加、删除和通知观察者的方法。
- 具体主题类:实现主题接口,维护观察者列表。
- 观察者接口/抽象类:定义更新方法。
- 具体观察者类:实现观察者接口,执行具体更新逻辑。
特点
- 解耦:观察者和主题之间的低耦合。
- 灵活性:观察者可以在运行时动态添加或删除。
核心点
- 维护一个观察者列表。
- 通知所有观察者更新。
实现
#include <iostream>
#include <vector>
#include <algorithm>
// 抽象观察者类,定义了更新接口
class Observer
{
public:
// 纯虚函数,要求子类必须实现,用于接收状态更新
virtual void update(int state) = 0;
};
// 主题类,管理观察者并通知状态变化
class Subject
{
private:
// 存储所有观察者的指针
std::vector<Observer*> observers;
// 保存主题的状态
int state;
public:
// 添加观察者
void attach(Observer* observer)
{
observers.push_back(observer);
}
// 移除观察者
void detach(Observer* observer)
{
// 使用std::remove-erase惯用法从向量中移除观察者
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
}
// 通知所有观察者状态改变
void notify()
{
for (Observer* observer : observers)
{
observer->update(state);
}
}
// 设置新状态并通知观察者
void setState(int state)
{
this->state = state;
notify();
}
// 获取当前状态
int getState() const
{
return state;
}
};
// 具体观察者类,实现了Observer接口
class ConcreteObserver : public Observer
{
private:
// 观察者的名字
std::string name;
// 对主题对象的引用
Subject& subject;
public:
// 构造函数,初始化观察者名字和主题
ConcreteObserver(std::string name, Subject& subject) : name(name), subject(subject) {}
// 实现update方法,用于接收状态更新
void update(int state) override
{
std::cout << "Observer " << name << " received update: " << state << std::endl;
}
};
代码解析
抽象观察者类
Observer
Observer
类是一个抽象基类,包含一个纯虚函数update(int state)
,表示观察者接收状态更新的方法。- 所有继承自
Observer
的具体观察者类必须实现该方法。主题类
Subject
Subject
类管理着所有的观察者,并负责在状态变化时通知这些观察者。attach
方法用于添加观察者。detach
方法用于移除观察者,使用了std::remove-erase
惯用法。notify
方法用于通知所有观察者当前的状态。setState
方法用于设置新的状态,并通知所有观察者。getState
方法用于获取当前状态。具体观察者类
ConcreteObserver
ConcreteObserver
类继承自Observer
,并实现了update
方法。- 在
update
方法中,输出接收到的状态更新。ConcreteObserver
还包含了观察者的名字和对Subject
对象的引用。
main
函数
- 在
main
函数中,首先创建了Subject
对象subject
。- 然后创建了两个
ConcreteObserver
对象observer1
和observer2
,并将它们关联到subject
。- 将观察者附加到主题对象中。
- 改变主题对象的状态,观察者会自动接收到状态的变化。
- 移除一个观察者,并再次改变主题状态,只有剩下的观察者会收到通知。
整个代码展示了观察者模式的应用。在观察者模式中,主题对象管理着所有的观察者,并在状态变化时通知这些观察者,从而实现了解耦和灵活性。
适用场景
- 一个对象的改变需要触发其他对象的变化。
- 不知道具体有多少对象需要被通知。
经典示例实现
#include <iostream>
#include <vector>
#include <algorithm>
// 抽象观察者类,定义了更新接口
class Observer
{
public:
// 纯虚函数,要求子类必须实现,用于接收状态更新
virtual void update(int state) = 0;
};
// 主题类,管理观察者并通知状态变化
class Subject
{
private:
// 存储所有观察者的指针
std::vector<Observer*> observers;
// 保存主题的状态
int state;
public:
// 添加观察者
void attach(Observer* observer)
{
observers.push_back(observer);
}
// 移除观察者
void detach(Observer* observer)
{
// 使用std::remove-erase惯用法从向量中移除观察者
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
}
// 通知所有观察者状态改变
void notify()
{
for (Observer* observer : observers)
{
observer->update(state);
}
}
// 设置新状态并通知观察者
void setState(int state)
{
this->state = state;
notify();
}
// 获取当前状态
int getState() const
{
return state;
}
};
// 具体观察者类,实现了Observer接口
class ConcreteObserver : public Observer
{
private:
// 观察者的名字
std::string name;
// 对主题对象的引用
Subject& subject;
public:
// 构造函数,初始化观察者名字和主题
ConcreteObserver(std::string name, Subject& subject) : name(name), subject(subject) {}
// 实现update方法,用于接收状态更新
void update(int state) override
{
std::cout << "Observer " << name << " received update: " << state << std::endl;
}
};
int main()
{
// 创建主题对象
Subject subject;
// 创建具体观察者对象,并关联到主题
ConcreteObserver observer1("Observer1", subject);
ConcreteObserver observer2("Observer2", subject);
// 将观察者附加到主题
subject.attach(&observer1);
subject.attach(&observer2);
// 改变主题状态,会自动通知所有观察者
subject.setState(1);
subject.setState(2);
// 将一个观察者从主题中移除
subject.detach(&observer1);
// 再次改变主题状态,只有剩下的观察者会收到通知
subject.setState(3);
return 0;
}
代码解析
Observer
类:定义观察者接口。Subject
类:维护观察者列表,状态更新时通知观察者。ConcreteObserver
类:实现观察者接口,处理更新逻辑。
4. 策略模式(Strategy)
概念
策略模式定义一系列算法,把它们一个个封装起来,并且使它们可互相替换,使得算法可以独立于使用它的客户端而变化。
模型
- 策略接口/抽象类:定义算法接口。
- 具体策略类:实现具体算法。
- 上下文类:持有策略对象,并通过策略接口调用具体算法。
特点
- 算法的独立性:算法可以独立于上下文变化。
- 灵活性:可以动态更改算法。
核心点
- 定义策略接口。
- 上下文持有策略对象,通过接口调用算法。
实现
class Strategy
{
public:
virtual void algorithm() = 0;
};
class ConcreteStrategyA : public Strategy
{
public:
void algorithm() override { std::cout << "Algorithm A" << std::endl; }
};
class ConcreteStrategyB : public Strategy
{
public:
void algorithm() override { std::cout << "Algorithm B" << std::endl; }
};
class Context
{
private:
Strategy* strategy;
public:
Context(Strategy* strategy) : strategy(strategy) {}
void setStrategy(Strategy* strategy)
{
this->strategy = strategy;
}
void executeStrategy()
{
strategy->algorithm();
}
};
适用场景
- 需要使用不同的算法解决问题,并且可以在运行时切换算法。
- 避免使用多重条件语句(if-else或switch-case)。
经典示例实现
#include <iostream>
class Strategy
{
public:
virtual void algorithm() = 0;
};
class ConcreteStrategyA : public Strategy
{
public:
void algorithm() override { std::cout << "Algorithm A" << std::endl; }
};
class ConcreteStrategyB : public Strategy
{
public:
void algorithm() override { std::cout << "Algorithm B" << std::endl; }
};
class Context
{
private:
Strategy* strategy;
public:
Context(Strategy* strategy) : strategy(strategy) {}
void setStrategy(Strategy* strategy)
{
this->strategy = strategy;
}
void executeStrategy()
{
strategy->algorithm();
}
};
int main()
{
ConcreteStrategyA strategyA;
ConcreteStrategyB strategyB;
Context context(&strategyA);
context.executeStrategy();
context.setStrategy(&strategyB);
context.executeStrategy();
return 0;
}
代码解析
Strategy
类:定义算法接口。ConcreteStrategyA
和ConcreteStrategyB
类:实现具体算法。Context
类:持有策略对象,通过接口调用算法。
5. 适配器模式(Adapter)
概念
适配器模式将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
模型
- 目标接口:定义客户所期待的接口。
- 适配者类:定义现有接口。
- 适配器类:通过组合或继承,将适配者接口转换为目标接口。
特点
- 重新利用现有代码:通过适配器,使得现有接口与新接口兼容。
- 解耦:客户端代码与适配者类解耦。
核心点
- 定义目标接口。
- 适配器类实现目标接口,通过组合或继承适配者类。
实现
class Target
{
public:
virtual void request() = 0;
};
class Adaptee
{
public:
void specificRequest() { std::cout << "Specific Request" << std::endl; }
};
class Adapter : public Target
{
private:
Adaptee* adaptee;
public:
Adapter(Adaptee* adaptee) : adaptee(adaptee) {}
void request() override
{
adaptee->specificRequest();
}
};
适用场景
- 需要将现有类的接口转换为另一接口。
- 使得原本不兼容的类一起工作。
经典示例实现
#include <iostream>
class Target
{
public:
virtual void request() = 0;
};
class Adaptee
{
public:
void specificRequest() { std::cout << "Specific Request" << std::endl; }
};
class Adapter : public Target
{
private:
Adaptee* adaptee;
public:
Adapter(Adaptee* adaptee) : adaptee(adaptee) {}
void request() override
{
adaptee->specificRequest();
}
};
int main()
{
Adaptee adaptee;
Adapter adapter(&adaptee);
adapter.request();
return 0;
}
代码解析
Target
类:定义客户所期待的接口。Adaptee
类:定义现有接口,需要被适配。Adapter
类:实现目标接口,通过组合适配者类,将其接口转换为目标接口。
6. 现代设计模式
6.1 Type Erasure
概念
Type Erasure是一种技术,使得在运行时仍能使用不同类型的对象,而不需要在编译时知道具体类型。
模型
- 接口类:定义统一接口。
- 具体实现类:使用模板类实现接口。
- 持有器类:持有具体实现对象,通过接口类访问其功能。
特点
- 灵活性:可以处理不同类型的对象,而不需要在编译时知道具体类型。
- 扩展性:可以添加新类型的对象,而不需要修改现有代码。
核心点
- 定义统一接口。
- 使用模板类实现接口,并持有具体实现对象。
实现
class AnyFunction
{
public:
virtual ~AnyFunction() = default;
virtual void invoke() const = 0;
};
template <typename Func>
class ConcreteFunction : public AnyFunction
{
private:
Func func;
public:
ConcreteFunction(Func func) : func(func) {}
void invoke() const override { func(); }
};
class TypeErasedFunction
{
private:
AnyFunction* func;
public:
template <typename Func>
TypeErasedFunction(Func func) : func(new ConcreteFunction<Func>(func)) {}
~TypeErasedFunction()
{
delete func;
}
void operator()() const
{
func->invoke();
}
};
适用场景
- 需要处理不同类型的对象,而不需要在编译时知道具体类型。
- 需要灵活扩展新类型的对象。
经典示例实现
#include <iostream>
class AnyFunction
{
public:
virtual ~AnyFunction() = default;
virtual void invoke() const = 0;
};
template <typename Func>
class ConcreteFunction : public AnyFunction
{
private:
Func func;
public:
ConcreteFunction(Func func) : func(func) {}
void invoke() const override { func(); }
};
class TypeErasedFunction
{
private:
AnyFunction* func;
public:
template <typename Func>
TypeErasedFunction(Func func) : func(new ConcreteFunction<Func>(func)) {}
~TypeErasedFunction()
{
delete func;
}
void operator()() const
{
func->invoke();
}
};
void exampleFunction()
{
std::cout << "Example Function" << std::endl;
}
int main()
{
TypeErasedFunction func(exampleFunction);
func();
return 0;
}
代码解析
AnyFunction
类:定义统一接口。ConcreteFunction
类:使用模板类实现接口,持有具体实现对象。TypeErasedFunction
类:持有具体实现对象,通过接口类访问其功能。
6.2 Curiously Recurring Template Pattern (CRTP)
概念
CRTP是一种C++编程技巧,其中一个类将自己作为模板参数传递给基类。
模型
- 基类模板:使用派生类作为模板参数。
- 派生类:继承基类模板,并将自己作为模板参数传递给基类模板。
特点
- 静态多态:在编译时决定类型和调用方法,而不是运行时。
- 无需虚函数开销:避免了虚函数的运行时开销。
核心点
- 基类模板使用派生类作为模板参数。
- 派生类继承基类模板,并将自己作为模板参数传递给基类模板。
实现
template <typename Derived>
class Base
{
public:
void interface()
{
static_cast<Derived*>(this)->implementation();
}
void implementation()
{
std::cout << "Base implementation" << std::endl;
}
};
class Derived : public Base<Derived>
{
public:
void implementation()
{
std::cout << "Derived implementation" << std::endl;
}
};
适用场景
- 需要静态多态的场景,如静态接口仿真。
- 希望避免虚函数开销的场景。
经典示例实现
#include <iostream>
template <typename Derived>
class Base
{
public:
void interface()
{
static_cast<Derived*>(this)->implementation();
}
void implementation()
{
std::cout << "Base implementation" << std::endl;
}
};
class Derived : public Base<Derived>
{
public:
void implementation()
{
std::cout << "Derived implementation" << std::endl;
}
};
int main()
{
Derived d;
d.interface();
return 0;
}
代码解析
Base
模板类:定义接口方法interface
,通过静态转换调用派生类的实现。Derived
类:继承基类模板,并实现具体方法implementation
。main
函数:创建Derived
对象并调用接口方法interface
,展示静态多态。
特点
- 静态多态:在编译时确定调用的具体方法。
- 避免虚函数开销:CRTP通过模板实现多态,避免了虚函数的运行时开销。
总结
本文详细介绍了几种经典和现代设计模式在C++中的应用。每种设计模式都包含其概念、模型、特点、核心点、实现方法、适用场景,以及经典示例代码和详细解析。
- 单例模式:确保一个类只有一个实例,并提供一个全局访问点。适用于需要唯一实例的场景,如日志记录器。
- 工厂模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类。适用于客户端不需要知道具体产品类的创建逻辑的场景。
- 观察者模式:定义对象间的一种一对多依赖关系,当一个对象的状态改变时,所有依赖它的对象都会收到通知并自动更新。适用于一个对象的改变需要触发其他对象的变化的场景。
- 策略模式:定义一系列算法,把它们一个个封装起来,使它们可以互相替换。适用于需要使用不同的算法解决问题的场景。
- 适配器模式:将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。适用于需要将现有类的接口转换为另一接口的场景。
- Type Erasure:通过运行时多态和模板实现类型擦除,使不同类型的对象可以通过统一接口访问。适用于需要处理不同类型的对象而不需要在编译时知道具体类型的场景。
- CRTP:通过模板实现静态多态,避免虚函数开销。适用于需要静态多态的场景,并希望避免虚函数开销。
这些设计模式不仅能帮助开发者编写更易维护、更具扩展性的代码,还能提升代码的灵活性和复用性。通过结合这些设计模式,开发者可以更好地应对软件开发中的复杂问题,提高软件质量和开发效率。