设计模式
设计模式概述
设计模式是软件设计中常见问题的典型解决方案。它们像是预定义的蓝图,可以帮助解决代码中常见的设计问题。设计模式不是可以直接转换为代码的完整设计,而是解决特定问题的模板。
设计模式的分类
- 创建型模式:处理对象创建机制
- 结构型模式:处理类和对象的组合
- 行为型模式:处理对象间的通信
装饰模式
装饰模式(Decorator Pattern)是一种结构型设计模式,允许向一个现有的对象添加新的功能,同时又不改变其结构。
1. 装饰模式的特点
- 动态地给一个对象添加一些额外的职责
- 相比生成子类更为灵活
- 通过组合而非继承的方式扩展功能
2. 装饰模式的组成
- 抽象组件(Component): 定义对象的接口
- 具体组件(ConcreteComponent): 实现组件接口
- 抽象装饰(Decorator): 继承/实现组件,并持有组件引用
- 具体装饰(ConcreteDecorator): 实现装饰功能
装饰模式的实现
实现原理
装饰模式采用”继承+组合”的双重方式,这是经过深思熟虑的设计选择:
- 继承组件类的目的:
- 保证装饰器与被装饰对象具有相同的接口
- 实现”透明装饰”:客户端代码无需知道处理的是原始对象还是装饰后的对象
- 使装饰器可以递归嵌套多层
- 持有组件实例的目的:
- 实现功能的动态组合
- 可以在运行时灵活地添加或移除功能
- 避免通过继承导致的类爆炸问题
示例说明:
// 原始组件
Component* obj = new ConcreteComponent();
// 第一层装饰 - 添加功能A
obj = new ConcreteDecoratorA(obj);
// 第二层装饰 - 添加功能B
obj = new ConcreteDecoratorB(obj);
// 调用时会依次执行B->A->原始功能
obj->operation();
基本结构
// 抽象组件
class Component {
public:
virtual void operation() = 0;
virtual ~Component() {}
};
// 具体组件
class ConcreteComponent : public Component {
public:
void operation() override {
// 基本功能实现
}
};
// 抽象装饰
class Decorator : public Component {
protected:
Component* component;
public:
Decorator(Component* c) : component(c) {}
void operation() override {
if (component) component->operation();
}
};
// 具体装饰
class ConcreteDecoratorA : public Decorator {
public:
ConcreteDecoratorA(Component* c) : Decorator(c) {}
void operation() override {
Decorator::operation();
// 添加的额外功能
}
};
使用示例
// 具体装饰B - 添加新功能
class ConcreteDecoratorB : public Decorator {
public:
ConcreteDecoratorB(Component* c) : Decorator(c) {}
void operation() override {
Decorator::operation(); // 先调用被装饰对象
std::cout << "装饰B添加的功能" << std::endl;
}
};
// 具体装饰C - 添加新功能
class ConcreteDecoratorC : public Decorator {
public:
ConcreteDecoratorC(Component* c) : Decorator(c) {}
void operation() override {
std::cout << "装饰C前置处理" << std::endl;
Decorator::operation(); // 调用被装饰对象
std::cout << "装饰C后置处理" << std::endl;
}
};
int main() {
// 1. 创建原始组件
Component* obj = new ConcreteComponent();
// 2. 第一层装饰 - 添加功能A
obj = new ConcreteDecoratorA(obj);
// 3. 第二层装饰 - 添加功能B
obj = new ConcreteDecoratorB(obj);
// 4. 第三层装饰 - 添加功能C
obj = new ConcreteDecoratorC(obj);
// 调用顺序说明:
// 1. 先进入ConcreteDecoratorC::operation()
// 2. 然后进入ConcreteDecoratorB::operation()
// 3. 接着进入ConcreteDecoratorA::operation()
// 4. 最后执行ConcreteComponent::operation()
// 5. 然后按相反顺序返回执行各装饰器的后续代码
obj->operation();
// 输出结果:
// 装饰C前置处理
// 装饰A添加的功能
// 装饰B添加的功能
// 基础组件功能
// 装饰C后置处理
delete obj; // 会自动递归删除所有装饰层
return 0;
}
装饰模式的应用场景
| 场景 | 说明 |
|---|---|
| 动态添加功能 | 运行时动态添加功能,不影响其他对象 |
| 替代子类扩展 | 避免子类膨胀问题 |
| 撤销功能 | 可以方便地移除装饰 |
装饰模式的优缺点
优点
- 比继承更灵活
- 可以动态添加或删除功能
- 符合开闭原则
缺点
- 会产生许多小对象
- 调试和排查问题较复杂
- 多层装饰时可能难以理解
工厂模式
工厂模式(Factory Pattern)是一种创建型设计模式,它提供了一种创建对象的最佳方式。工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。
1. 工厂模式的特点
- 将对象的创建与使用分离
- 通过工厂方法替代new操作符
- 客户端无需知道具体产品类名
- 符合开闭原则,易于扩展
2. 工厂模式的组成
- 抽象产品(Product): 定义产品的接口
- 具体产品(ConcreteProduct): 实现产品接口
- 抽象工厂(Creator): 声明工厂方法
- 具体工厂(ConcreteCreator): 实现工厂方法
工厂模式的实现
实现原理
工厂方法模式通过让子类决定创建什么对象来实现对象的创建过程封装:
- 抽象产品定义产品接口
- 具体产品实现产品接口
- 抽象工厂声明工厂方法
- 具体工厂实现工厂方法创建具体产品
示例说明:
// 抽象产品
class Product {
public:
virtual ~Product() {}
virtual void operation() = 0;
};
// 具体产品A
class ConcreteProductA : public Product {
public:
void operation() override {
std::cout << "ConcreteProductA operation" << std::endl;
}
};
// 具体产品B
class ConcreteProductB : public Product {
public:
void operation() override {
std::cout << "ConcreteProductB operation" << std::endl;
}
};
// 抽象工厂
class Creator {
public:
virtual ~Creator() {}
virtual Product* factoryMethod() = 0;
};
// 具体工厂A
class ConcreteCreatorA : public Creator {
public:
Product* factoryMethod() override {
return new ConcreteProductA();
}
};
// 具体工厂B
class ConcreteCreatorB : public Creator {
public:
Product* factoryMethod() override {
return new ConcreteProductB();
}
};
int main() {
Creator* creatorA = new ConcreteCreatorA();
Product* productA = creatorA->factoryMethod();
productA->operation();
Creator* creatorB = new ConcreteCreatorB();
Product* productB = creatorB->factoryMethod();
productB->operation();
delete productA;
delete creatorA;
delete productB;
delete creatorB;
return 0;
}
工厂模式的应用场景
| 场景 | 说明 |
|---|---|
| 类无法预知需要创建的对象类型 | 由子类决定创建的具体类 |
| 希望将产品创建过程解耦 | 将创建逻辑与使用逻辑分离 |
| 需要灵活扩展产品类型 | 只需添加新的具体工厂类 |
工厂模式的优缺点
优点
- 避免创建者和具体产品之间的紧密耦合
- 单一职责原则:创建代码放在单一位置
- 开闭原则:无需修改现有代码即可引入新产品
缺点
- 需要引入许多子类,可能增加代码复杂度
- 对于简单对象创建可能过度设计
适配器模式
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许接口不兼容的类可以一起工作。适配器充当两个不兼容接口之间的桥梁。
1. 适配器模式的特点
- 将一个类的接口转换成客户期望的另一个接口
- 让原本接口不兼容的类可以合作
- 分为类适配器和对象适配器两种实现方式
2. 适配器模式的组成
- 目标接口(Target): 客户期望的接口
- 需要适配的类(Adaptee): 需要被适配的已有类
- 适配器(Adapter): 实现目标接口并包装Adaptee
适配器模式的实现
对象适配器实现
// 目标接口
class Target {
public:
virtual void request() = 0;
virtual ~Target() {}
};
// 需要适配的类
class Adaptee {
public:
void specificRequest() {
std::cout << "Adaptee的特定请求" << std::endl;
}
};
// 适配器
class Adapter : public Target {
private:
Adaptee* adaptee;
public:
Adapter(Adaptee* a) : adaptee(a) {}
void request() override {
adaptee->specificRequest(); // 调用被适配者的方法
}
};
// 使用示例
int main() {
Adaptee* adaptee = new Adaptee();
Target* target = new Adapter(adaptee);
target->request(); // 输出: Adaptee的特定请求
delete target;
delete adaptee;
return 0;
}
类适配器实现
// 目标接口
class Target {
public:
virtual void request() = 0;
virtual ~Target() {}
};
// 需要适配的类
class Adaptee {
public:
void specificRequest() {
std::cout << "Adaptee的特定请求" << std::endl;
}
};
// 类适配器(通过多重继承)
class Adapter : public Target, private Adaptee {
public:
void request() override {
specificRequest(); // 直接调用继承的方法
}
};
// 使用示例
int main() {
Target* target = new Adapter();
target->request(); // 输出: Adaptee的特定请求
delete target;
return 0;
}
适配器模式的应用场景
| 场景 | 说明 |
|---|---|
| 系统需要使用现有类 | 但这些类的接口不符合系统要求 |
| 第三方组件集成 | 需要将第三方组件接口转换为系统定义的接口 |
| 统一多个子系统的接口 | 当多个子系统提供类似功能但接口不同时 |
适配器模式的优缺点
优点
- 可以让任何两个没有关联的类一起运行
- 提高了类的复用性
- 增加了类的透明度
- 灵活性好
缺点
- 过多使用适配器会让系统混乱
- 增加系统复杂度,需要额外维护适配器类
XML转JSON适配器示例
#include <iostream>
#include <string>
#include <map>
// JSON目标接口(客户端期望的接口)
class JSONTarget {
public:
virtual std::string getJSONData() = 0;
virtual ~JSONTarget() {}
};
// XML数据源(需要适配的类)
class XMLAdaptee {
private:
std::string xmlData;
public:
XMLAdaptee(const std::string& data) : xmlData(data) {}
std::string getXMLData() {
return xmlData;
}
// 模拟XML解析方法
std::map<std::string, std::string> parseXML() {
// 这里简化实现,实际应使用XML解析器
std::map<std::string, std::string> result;
result["name"] = "张三";
result["age"] = "30";
result["city"] = "北京";
return result;
}
};
// XML转JSON适配器
class XMLToJSONAdapter : public JSONTarget {
private:
XMLAdaptee* xmlAdaptee;
public:
XMLToJSONAdapter(XMLAdaptee* adaptee) : xmlAdaptee(adaptee) {}
std::string getJSONData() override {
// 1. 解析XML数据
auto parsedData = xmlAdaptee->parseXML();
// 2. 转换为JSON格式
std::string json = "{";
for (const auto& pair : parsedData) {
json += "\"" + pair.first + "\":\"" + pair.second + "\",";
}
json.pop_back(); // 移除最后一个逗号
json += "}";
return json;
}
};
// 客户端代码(只兼容JSON接口)
void clientCode(JSONTarget* target) {
std::cout << "获取JSON数据: " << target->getJSONData() << std::endl;
}
int main() {
// 1. 创建XML数据源
XMLAdaptee* xmlSource = new XMLAdaptee("<user><name>张三</name><age>30</age><city>北京</city></user>");
// 2. 创建适配器
JSONTarget* adapter = new XMLToJSONAdapter(xmlSource);
// 3. 客户端使用JSON接口
clientCode(adapter);
delete adapter;
delete xmlSource;
return 0;
}
/* 输出结果:
获取JSON数据: {"name":"张三","age":"30","city":"北京"}
*/
享元模式
享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享技术来有效地支持大量细粒度对象的复用。
1. 享元模式的特点
- 通过共享技术减少内存使用
- 适合大量相似对象的场景
- 将对象的状态分为内部状态和外部状态
- 内部状态可以共享,外部状态由客户端维护
2. 享元模式的组成
- 享元接口(Flyweight): 定义对象的接口
- 具体享元(ConcreteFlyweight): 实现享元接口,包含内部状态
- 非共享具体享元(UnsharedConcreteFlyweight): 不共享的享元实现
- 享元工厂(FlyweightFactory): 创建和管理享元对象
享元模式的实现
基本结构
#include <iostream>
#include <string>
#include <map>
// 享元接口
class Flyweight {
public:
virtual void operation(const std::string& extrinsicState) = 0;
virtual ~Flyweight() {}
};
// 具体享元
class ConcreteFlyweight : public Flyweight {
private:
std::string intrinsicState; // 内部状态
public:
ConcreteFlyweight(const std::string& state) : intrinsicState(state) {}
void operation(const std::string& extrinsicState) override {
std::cout << "内部状态: " << intrinsicState
<< ", 外部状态: " << extrinsicState << std::endl;
}
};
// 享元工厂
class FlyweightFactory {
private:
std::map<std::string, Flyweight*> flyweights;
public:
Flyweight* getFlyweight(const std::string& key) {
if (flyweights.find(key) == flyweights.end()) {
flyweights[key] = new ConcreteFlyweight(key);
}
return flyweights[key];
}
~FlyweightFactory() {
for (auto& pair : flyweights) {
delete pair.second;
}
}
};
// 客户端代码
int main() {
FlyweightFactory factory;
// 获取享元对象并设置外部状态
Flyweight* fw1 = factory.getFlyweight("A");
fw1->operation("状态1");
Flyweight* fw2 = factory.getFlyweight("A");
fw2->operation("状态2");
Flyweight* fw3 = factory.getFlyweight("B");
fw3->operation("状态3");
return 0;
}
/* 输出示例:
内部状态: A, 外部状态: 状态1
内部状态: A, 外部状态: 状态2
内部状态: B, 外部状态: 状态3
*/
享元模式的应用场景
| 场景 | 说明 |
|---|---|
| 大量相似对象 | 系统中有大量相似对象,导致内存开销大 |
| 对象状态可分 | 对象的大部分状态可以外部化 |
| 缓存需求 | 需要缓存对象以提高性能 |
享元模式的优缺点
优点
- 减少内存使用,提高性能
- 减少对象创建数量
- 外部状态相对独立,不影响内部状态
缺点
- 增加系统复杂度
- 需要分离内部状态和外部状态
- 线程安全问题需要注意
粒子系统案例
// 粒子系统示例 - 享元模式应用
class ParticleType { // 享元类
private:
std::string texture;
std::string color;
public:
ParticleType(const std::string& tex, const std::string& col)
: texture(tex), color(col) {}
void draw(int x, int y) const { // x,y是外部状态
std::cout << "在(" << x << "," << y << ")绘制粒子: "
<< texture << "-" << color << std::endl;
}
// 允许ParticleFactory访问私有成员
friend class ParticleFactory;
};
class ParticleFactory { // 享元工厂
private:
std::map<std::string, ParticleType*> types;
public:
ParticleType* getParticleType(const std::string& key) {
if (types.find(key) == types.end()) {
if (key == "fire") types[key] = new ParticleType("火焰", "红");
else if (key == "smoke") types[key] = new ParticleType("烟雾", "灰");
}
return types[key];
}
};
class Particle { // 包含外部状态
private:
ParticleType* type; // 共享的内部状态
int x, y; // 外部状态
public:
Particle(ParticleType* t, int x, int y) : type(t), x(x), y(y) {}
void draw() { type->draw(x, y); }
};
/*
* friend关键字说明:
* 1. 允许被授权的类或函数访问当前类的私有成员
* 2. 在ParticleType中声明ParticleFactory为友元类,
* 使得工厂可以访问ParticleType的私有构造函数
* 3. 使用场景:
* - 工厂模式中创建对象
* - 运算符重载
* - 需要紧密协作的类
*/
int main() {
ParticleFactory factory;
std::vector<Particle> particles;
for (int i = 0; i < 100; ++i) {
ParticleType* type = factory.getParticleType(i % 2 ? "fire" : "smoke");
particles.emplace_back(type, rand()%100, rand()%100);
}
for (auto& p : particles) {
p.draw();
}
return 0;
}