四、常见设计模式
)
通常,设计模式被分为三大类: 创建型(Creational) 、 结构型(Structural)和行为型(Behavioral) 。
本篇只介绍常见的设计模式,如果对其他设计模式感兴趣推荐阅读:GoF的《设计模式:可复用面向对象软件的基础》,其中包含了23种经典面向对象设计模式。
创建型模式
这类模式专注于对象的创建机制,旨在将对象的创建与使用分离,使系统不依赖于对象是如何被创建、组合和表示的。
单例模式
单例模式(Singleton)是确保一个类在整个程序生命周期内只有一个实例,并提供一个全局访问该实例的入口。
- 唯一实例:某个类只允许创建至多一个对象。
- 全局访问点 :通常通过静态成员函数(如
getInstance())返回该唯一实例的引用或指针。 - 应用场景:日志管理器、配置管理器、全局状态管理。
- 缺点:谨慎使用单例,过多使用会掩盖设计缺陷,增加耦合。
单例模式有两种经典实现方式:饿汉模式 和懒汉模式。
饿汉模式
饿汉式(Eager Initialization):在程序启动、main 执行之前就创建实例。利用了静态成员变量在程序进入 main 前已完成初始化的特性。
特点:
- 线程安全:主线程启动前完成构造,不存在多线程竞态(但不绝对,若多个静态对象跨编译单元,初始化顺序未定义)。
- 缺点:即使从未使用该实例,它也会被创建,可能浪费资源,且若构造函数依赖其他尚未初始化的静态对象则可能引发"静态初始化顺序问题"。但是这个问题在 C++17 已经可以解决了。C++17 允许在类内定义
inline静态成员变量。inline保证该变量在所有编译单元中共享同一实体,且能在头文件中定义,无需 .cpp 定义。总的来说缺点仍然是直接创建,可能浪费资源。
最佳实践
c
// C++17及以上
class Singleton
{
public:
static Singleton& getInstance()
{
return _instance;
}
// 禁止拷贝和赋值
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;
private:
Singleton() = default;
static Singleton _instance; // C++17 inline static
};
// 类外定义,可以在.h定义,因为用了 inline 避免重复定义
inline Singleton Singleton::_instance;
c
// C++17以下
class Singleton
{
public:
static Singleton &getInstance()
{
return _instance;
}
// 禁止拷贝和赋值
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;
private:
Singleton() = default; // 构造函数私有
static Singleton _instance; // 静态实例
};
// 在类外定义并初始化
Singleton Singleton::_instance; // 不要在.h中定义,否则可能会在多个.cpp中包含(头源分离情况)
懒汉模式
懒汉式(Lazy Initialization):在首次调用 getInstance() 时才创建实例,延迟初始化。
最佳实践
c
class Singleton
{
public:
static Singleton& getInstance()
{
// C++11起保证线程安全的懒初始化
static Singleton instance;
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
};
演变过程-非必看
非线程安全版本
c
class Singleton
{
public:
static Singleton *getInstance()
{
if (!_instance)
{
_instance = new Singleton();
}
return _instance;
}
// 禁止拷贝和赋值
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;
private:
Singleton() = default; // 构造函数私有
static Singleton *_instance; // 静态实例
};
// 类外定义和初始化
Singleton *Singleton::_instance = nullptr;
线程安全版本
真正实现时,要注意线程安全问题。因为懒汉式是在首次掉用时才创建实例,多线程高并发下,可能会同时创建多个实例,所以需要加上互斥锁。为了避免每次调用都加锁的性能开销,尝试在锁外加一层检查。
c
#include <mutex>
class Singleton
{
public:
static Singleton *getInstance()
{
// 第一次检查
if (!_instance)
{
// RAII 锁,保证线程安全
{
std::lock_guard<std::mutex> lock(_mtx);
// 第二次检查
if (!_instance)
{
_instance = new Singleton();
}
}
}
return _instance;
}
private:
Singleton() = default;
static Singleton *_instance;
static std::mutex _mtx;
};
Singleton *Singleton::_instance = nullptr;
std::mutex Singleton::_mtx;
在 C++11 之前,这段代码存在指令重排导致的不安全 。new Singleton() 可分解为:分配内存、调用构造函数、将地址赋给 instance。如果编译器或 CPU 将赋值提前(instance 先指向了未构造完成的内存),另一线程的第一次检查就会拿到一个未完全初始化的对象,造成未定义行为。C++11 之后,若 instance 用 std::atomic 修饰,并使用适当内存序, 双重检查锁定才可以正确工作。
c
// C++11
#include <mutex>
#include <memory>
class Singleton
{
public:
static Singleton &getInstance()
{
std::call_once(_initFlag, &Singleton::initSingleton);
return *_instance;
}
private:
Singleton() = default;
~Singleton() = default;
static void initSingleton()
{
_instance.reset(new Singleton);
}
static std::unique_ptr<Singleton> _instance;
static std::once_flag _initFlag;
};
std::unique_ptr<Singleton> Singleton::_instance;
std::once_flag Singleton::_initFlag;
工厂模式
工厂模式(Factory Pattern) :将对象的创建与使用分离,让客户端无需关心具体类的实例化细节,只需通过一个通用接口获取对象。这种解耦方式极大地提升了系统的可扩展性和可维护性。
首先思考一个问题:为什么需要工厂模式?
如果直接使用 new 或 std::make_shared 创建对象会造成:
- 违背开闭原则:每次新增一种产品,都要修改客户端代码中具体类的名字。
- 耦合高,客户端必须知道具体类的定义,一旦具体类变化,大量代码需要重新编译。
- 复杂构造过程:某些对象的创建需要一系列参数准备、依赖注入、缓存或池化等操作,这些逻辑不应直接在客户端。
工厂模式把创建对象的过程封装,客户端只依赖抽象接口。
工厂模式需根据实际场景选择性使用。
简单工厂模式
简单工厂不是 GoF 的 23 种设计模式之一,而是一种编程习惯。它用一个工厂类,根据传入的参数决定具体创建。
特点:
- 优点:客户端只需要知道工厂和抽象产品,无需关心具体类。
- 缺点 :工厂类集中了所有创建逻辑,违反开闭原则。新增产品必须修改工厂的
if-else或switch-case。适合产品种类固定、数量较少的场景。
c
#include <iostream>
#include <memory>
#include <string>
// 抽象产品
class Food
{
public:
virtual void specificFood() const = 0;
virtual ~Food() = default;
};
// 具体产品
class CatFood : public Food
{
public:
void specificFood() const override
{
std::cout << "猫粮" << std::endl;
}
};
class DogFood : public Food
{
public:
void specificFood() const override
{
std::cout << "狗粮" << std::endl;
}
};
// 简单工厂
class FoodFactory
{
public:
static std::unique_ptr<Food> createFood(const std::string &type)
{
if (type == "cat")
{
return std::make_unique<CatFood>();
}
else if (type == "dog")
{
return std::make_unique<DogFood>();
}
// 未知类型,抛异常
throw std::invalid_argument("Unknown food type");
}
};
// 客户端
int main()
{
auto t = FoodFactory::createFood("cat");
t->specificFood();
return 0;
}
工厂方法模式
工厂方法模式将对象的创建延迟到子类,即定义一个创建对象的接口,让子类决定实例化哪一个类。
特点:
- 单一职责:每个具体创建者只负责一种具体产品。
- 开闭原则:增加新产品时,只需新增具体产品类和对应的具体工厂类,无需修改原有代码。
- C++ 实现中,返回类型通常使用
std::unique_ptr以明确所有权并避免内存泄漏。基类必须有虚析构函数。
c
#include <iostream>
#include <memory>
#include <string>
// 抽象产品
class Food
{
public:
virtual void specificFood() = 0;
virtual ~Food() = default;
};
// 具体产品
class CatFood : public Food
{
public:
void specificFood() override
{
std::cout << "猫粮" << std::endl;
}
};
class DogFood : public Food
{
public:
void specificFood() override
{
std::cout << "狗粮" << std::endl;
}
};
// 抽象创建者
class Dialog
{
public:
// 工厂方法
virtual std::unique_ptr<Food> createFood() = 0;
void renderDialog()
{
// 调用工厂方法创建产品,不知道具体类型
auto food = createFood();
food->specificFood();
}
virtual ~Dialog() = default;
};
// 具体创建者
class CatFoodDialog : public Dialog
{
public:
std::unique_ptr<Food> createFood() override
{
return std::make_unique<CatFood>();
}
};
class DogFoodDialog : public Dialog
{
public:
std::unique_ptr<Food> createFood() override
{
return std::make_unique<DogFood>();
}
};
// 客户端
void clientCode(Dialog &dialog)
{
dialog.renderDialog();
}
int main()
{
CatFoodDialog catDialog;
clientCode(catDialog); // 输出猫粮
DogFoodDialog dogDialog;
clientCode(dogDialog); // 输出狗粮
return 0;
}
抽象工厂模式
抽象工厂提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。可简单理解为"工厂的工厂"。
特点:
- 系统需要由多个产品系列中的一个来配置。
- 需要保证一系列产品对象的一致性。
- 增加新产品族需要修改抽象工厂接口和所有具体工厂实现,违反开闭原则。但增加新的产品族则非常容易。
c
#include <iostream>
#include <memory>
#include <string>
// 抽象产品 A
class FruitFood
{
public:
virtual void eat() = 0;
virtual ~FruitFood() = default;
};
// 抽象产品 B
class VegetableFood
{
public:
virtual void eat() = 0;
virtual ~VegetableFood() = default;
};
// 具体产品
class SupermarketFruit : public FruitFood
{
public:
void eat() override { std::cout << "SupermarketFruit\n"; } // 直接使用\n即可,此时没必要直接使用std::endl刷新缓冲区
};
class SupermarketVegetable : public VegetableFood
{
public:
void eat() override { std::cout << "SupermarketVegetable\n"; }
};
// 具体产品
class StreetFruit : public FruitFood
{
public:
void eat() override { std::cout << "StreetFruit\n"; }
};
class StreetVegetable : public VegetableFood
{
public:
void eat() override { std::cout << "StreetVegetable\n"; }
};
// 抽象工厂
class FoodFactory
{
public:
virtual std::unique_ptr<FruitFood> createFruitFood() = 0;
virtual std::unique_ptr<VegetableFood> createVegetableFood() = 0;
virtual ~FoodFactory() = default;
};
// 具体工厂
class SupermarketFactory : public FoodFactory
{
public:
std::unique_ptr<FruitFood> createFruitFood() override
{
return std::make_unique<SupermarketFruit>();
}
std::unique_ptr<VegetableFood> createVegetableFood() override
{
return std::make_unique<SupermarketVegetable>();
}
};
class StreetFactory : public FoodFactory
{
public:
std::unique_ptr<FruitFood> createFruitFood() override
{
return std::make_unique<StreetFruit>();
}
std::unique_ptr<VegetableFood> createVegetableFood() override
{
return std::make_unique<StreetVegetable>();
}
};
// 客户端
void BuyFood(FoodFactory &factory)
{
auto fruit = factory.createFruitFood();
auto vegetable = factory.createVegetableFood();
fruit->eat();
vegetable->eat();
}
int main()
{
SupermarketFactory supermarketFactory;
BuyFood(supermarketFactory);
StreetFactory streetFactory;
BuyFood(streetFactory);
return 0;
}
建造者模式
建造者模式(Builder) :将一个复杂对象的构建过程 与它的表示分离,使得同样的构建过程可以创建不同的表示。
优点
- 分离构建与表示:相同的构建过程可以创建不同的产品,客户端无需知道产品内部组成。
- 精细化控制:建造步骤可以被严格控制,可以加入校验、资源管理等。
- 复用性:导演与抽象建造者配合,当需要新的产品变体时,只需新增具体建造者,无需修改导演。
- 支持可选参数:通过流式建造者避免构造函数参数过多的问题,也避免了重叠构造器(telescoping constructor)的繁琐。
- 便于创建不可变对象:将构建过程与对象构造分离,确保产品一旦返回就不可修改。
缺点
- 代码量增加:需要为每个产品类型编写具体的建造者类,系统类数量增多。
- 产品必须具有共性:如果产品之间差异巨大,抽象建造者的接口可能难以统一。
- 不适合简单对象:对于只需少量参数即可创建的对象,使用建造者过度设计。
- 所有权与生命周期:经典实现需要注意产品所有权的传递,现代 C++ 可以通过智能指针缓解。
适用场景
- 构造一个复杂对象,其构建过程独立于组成该对象的部件。如解析一个复杂的协议数据包,先解析头部、再解析体、最后计算校验和。
- 需要生成不同表示的产品。如生成富文本、Markdown、PDF 等不同格式的文档。
- 对象的构造过程必须按特定顺序执行。如构建一个房屋:先打地基,再砌墙,最后装修。
- 需要避免构造器参数爆炸。当一个类有多个可选参数,且大多数情况下只需设置其中部分时,使用流式建造者比提供十几个构造函数更优雅。
- 需要创建不可变对象。将所需参数逐步收集到建造者中,最后一次性构造目标不可变对象。
原始指针实现
c
// 经典实现
#include <iostream>
#include <string>
#include <memory>
// 产品
class Food
{
public:
void setName(const std::string &name) { _name = name; }
void setPrice(double price) { _price = price; }
void setWeight(double weight) { _weight = weight; }
void show() const
{
std::cout << "Food [Name=" << _name << ", Price=" << _price << ", Weight=" << _weight << "]\n";
}
private:
std::string _name;
double _price;
double _weight;
};
// 抽象建造者
class FoodBuilder
{
public:
virtual ~FoodBuilder() = default;
virtual void buildName() = 0;
virtual void buildPrice() = 0;
virtual void buildWeight() = 0;
virtual Food *getResult() = 0;
};
// 具体建造者
class FruitFoodBuilder : public FoodBuilder
{
private:
Food *_food;
public:
FruitFoodBuilder() { _food = new Food(); }
~FruitFoodBuilder() { delete _food; }
void buildName() override { _food->setName("Apple"); }
void buildPrice() override { _food->setPrice(1.50); }
void buildWeight() override { _food->setWeight(0.20); }
Food *getResult() override { return _food; }
};
class VegetableFoodBuilder : public FoodBuilder
{
private:
Food *_food;
public:
VegetableFoodBuilder() { _food = new Food(); }
~VegetableFoodBuilder() { delete _food; }
void buildName() override { _food->setName("Potato"); }
void buildPrice() override { _food->setPrice(0.80); }
void buildWeight() override { _food->setWeight(0.25); }
Food *getResult() override { return _food; }
};
// 指挥者
class Director
{
FoodBuilder *_builder;
public:
void setBuilder(FoodBuilder *builder) { _builder = builder; }
void construct()
{
_builder->buildName();
_builder->buildPrice();
_builder->buildWeight();
}
};
// 客户端代码
int main()
{
Director director;
FruitFoodBuilder fruitBuilder;
director.setBuilder(&fruitBuilder);
director.construct();
Food *fruit = fruitBuilder.getResult();
fruit->show();
VegetableFoodBuilder vegetableBuilder;
director.setBuilder(&vegetableBuilder);
director.construct();
Food *vegetable = vegetableBuilder.getResult();
vegetable->show();
return 0;
}
缺点是原始指针的所有权混乱 :建造者内部 new 了产品,析构函数负责释放,可客户端又拿到了原始指针,让人误以为需要自己 delete,此时客户端如果真的再次释放,会造成对同一块内存的二次释放 。建议使用智能指针明确所有权。
智能指针
c
#include <iostream>
#include <string>
#include <memory>
// 产品
class Food
{
public:
void setName(const std::string &name) { _name = name; }
void setPrice(double price) { _price = price; }
void setWeight(double weight) { _weight = weight; }
void show() const
{
std::cout << "Food [Name=" << _name << ", Price=" << _price << ", Weight=" << _weight << "]\n";
}
private:
std::string _name;
double _price;
double _weight;
};
// 抽象建造者
class FoodBuilder
{
public:
virtual ~FoodBuilder() = default;
virtual void buildName() = 0;
virtual void buildPrice() = 0;
virtual void buildWeight() = 0;
// 返回 unique_ptr,转移所有权
virtual std::unique_ptr<Food> getResult() = 0;
};
// 具体建造者
class FruitFoodBuilder : public FoodBuilder
{
private:
std::unique_ptr<Food> _food;
public:
FruitFoodBuilder() { _food = std::make_unique<Food>(); }
~FruitFoodBuilder() = default;
void buildName() override { _food->setName("Apple"); }
void buildPrice() override { _food->setPrice(1.50); }
void buildWeight() override { _food->setWeight(0.20); }
std::unique_ptr<Food> getResult() override
{
return std::move(_food); // 转移所有权给调用者
}
};
class VegetableFoodBuilder : public FoodBuilder
{
private:
std::unique_ptr<Food> _food;
public:
VegetableFoodBuilder() { _food = std::make_unique<Food>(); }
~VegetableFoodBuilder() = default;
void buildName() override { _food->setName("Potato"); }
void buildPrice() override { _food->setPrice(0.80); }
void buildWeight() override { _food->setWeight(0.25); }
std::unique_ptr<Food> getResult() override
{
return std::move(_food); // 转移所有权给调用者
}
};
// 指挥者
class Director
{
FoodBuilder *_builder;
public:
void setBuilder(FoodBuilder *builder) { _builder = builder; }
void construct()
{
_builder->buildName();
_builder->buildPrice();
_builder->buildWeight();
}
};
// 客户端代码
int main()
{
Director director;
FruitFoodBuilder fruitBuilder;
director.setBuilder(&fruitBuilder); // Director 仍用原始指针,因为它不管理生命周期
director.construct();
auto fruit = fruitBuilder.getResult(); // fruit 是 unique_ptr
fruit->show();
// 无需 delete,自动释放
VegetableFoodBuilder vegetableBuilder;
director.setBuilder(&vegetableBuilder);
director.construct();
auto vegetable = vegetableBuilder.getResult();
vegetable->show();
// 自动释放
return 0;
}
流式接口
流式接口(Fluent Builder):省略导演(Director)和抽象建造者(Builder) ,直接让一个具体建造者类提供一系列链式配置方法,每个方法返回 *this,最后通过 build() 方法产生产品。这种方式让构建过程更简洁、灵活,尤其适合参数多且大部分可选的场景。
c
#include <iostream>
#include <string>
#include <memory>
#include <iostream>
#include <string>
#include <memory>
// 产品类保持不变
class Food
{
public:
void setName(const std::string &name) { _name = name; }
void setPrice(double price) { _price = price; }
void setWeight(double weight) { _weight = weight; }
void show() const
{
std::cout << "Food [Name=" << _name
<< ", Price=" << _price
<< ", Weight=" << _weight << "]\n";
}
private:
std::string _name;
double _price = 0.0;
double _weight = 0.0;
};
// 流式建造者
class FluentFoodBuilder
{
std::unique_ptr<Food> _food; // 内部持有正在构建的产品
public:
FluentFoodBuilder() : _food(std::make_unique<Food>()) {}
// 每个配置方法返回 *this,实现链式调用
FluentFoodBuilder &name(const std::string &name)
{
_food->setName(name);
return *this;
}
FluentFoodBuilder &price(double price)
{
_food->setPrice(price);
return *this;
}
FluentFoodBuilder &weight(double weight)
{
_food->setWeight(weight);
return *this;
}
// 构建完成,转移产品所有权
std::unique_ptr<Food> build()
{
return std::move(_food);
}
};
// 客户端使用流式接口
int main()
{
// 创建苹果
auto apple = FluentFoodBuilder()
.name("Apple")
.price(1.50)
.weight(0.20)
.build();
apple->show();
// 创建土豆
auto potato = FluentFoodBuilder()
.name("Potato")
.price(0.80)
.weight(0.25)
.build();
potato->show();
return 0;
}
原型模式
原型模式(Prototype Pattern) :通过拷贝(克隆)一个已有对象(原型)来创建新对象,而不是通过 new 重新实例化。它在 C++ 中的实现高度依赖拷贝构造函数和虚函数机制,并且对深/浅拷贝的管理有着较高的要求。
简单实现
最简单的原型模式实现:基类定义 virtual unique_ptr<Base> clone() const = 0;,派生类实现它,内部使用拷贝构造函数复制自身。
c
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
// 抽象原型
class Fruit
{
public:
virtual ~Fruit() = default;
virtual std::unique_ptr<Fruit> clone() const = 0;
virtual void show() const = 0;
};
// 具体原型
class Apple : public Fruit
{
std::string _name = "RedApple";
double _weight;
double _price;
public:
Apple(const std::string &n, double w, double p) : _name(n), _weight(w), _price(p) {}
Apple(const Apple &other) = default;
std::unique_ptr<Fruit> clone() const override
{
return std::make_unique<Apple>(*this);
}
void show() const override
{
std::cout << "Apple: name=" << _name << ", weight=" << _weight << ", price=" << _price << std::endl;
}
};
class Watermelon : public Fruit
{
std::string _name = "SeedlessWatermelon";
double _weight;
double _price;
public:
Watermelon(const std::string &n, double w, double p) : _name(n), _weight(w), _price(p) {}
Watermelon(const Watermelon &other) = default;
std::unique_ptr<Fruit> clone() const override
{
return std::make_unique<Watermelon>(*this);
}
void show() const override
{
std::cout << "Watermelon: name=" << _name << ", weight=" << _weight << ", price=" << _price << std::endl;
}
};
// 客户端代码
int main()
{
// 创建一个原型
std::unique_ptr<Fruit> appleProto = std::make_unique<Apple>("RedApple", 1.5, 2.0);
// 克隆两个对象
auto apple1 = appleProto->clone();
auto apple2 = appleProto->clone();
apple1->show();
apple2->show();
// 原型2
std::unique_ptr<Fruit> watermelonProto = std::make_unique<Watermelon>("SeedlessWatermelon", 5.0, 3.0);
auto watermelon1 = watermelonProto->clone();
watermelon1->show();
return 0;
}
原型模式的核心是拷贝。如果对象内部包含指针、动态资源、其他对象引用,则必须小心处理深浅拷贝问题。
- 浅拷贝(默认拷贝构造):只复制指针值,导致多个对象共享同一块动态内存,容易造成双重释放或意外修改。
- 深拷贝:复制指针所指向的内容,使每个克隆对象拥有独立的资源。
C++ 中,要确保拷贝构造函数执行深拷贝,如果类中包含不可拷贝的资源(如 unique_ptr),则默认拷贝构造函数会被删除,此时需要手动实现深拷贝逻辑。
原型管理器(原型工厂)
当系统中有多种原型,并且希望动态选择时,可以引入一个原型管理器,将原型实例存储在一个映射表中,客户端通过键值获取克隆对象。
原型管理器的作用是集中存储多个原型实例,让客户端通过键值(字符串、枚举等)来获取克隆对象,完全消除对具体类名的依赖。
c
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
// 抽象原型
class Fruit
{
public:
virtual ~Fruit() = default;
virtual std::unique_ptr<Fruit> clone() const = 0;
virtual void show() const = 0;
};
// 具体原型
class Apple : public Fruit
{
std::string _name = "RedApple";
double _weight;
double _price;
public:
Apple(const std::string &n, double w, double p) : _name(n), _weight(w), _price(p) {}
Apple(const Apple &other) = default;
std::unique_ptr<Fruit> clone() const override
{
return std::make_unique<Apple>(*this);
}
void show() const override
{
std::cout << "Apple: name=" << _name << ", weight=" << _weight << ", price=" << _price << std::endl;
}
};
class Watermelon : public Fruit
{
std::string _name = "SeedlessWatermelon";
double _weight;
double _price;
public:
Watermelon(const std::string &n, double w, double p) : _name(n), _weight(w), _price(p) {}
Watermelon(const Watermelon &other) = default;
std::unique_ptr<Fruit> clone() const override
{
return std::make_unique<Watermelon>(*this);
}
void show() const override
{
std::cout << "Watermelon: name=" << _name << ", weight=" << _weight << ", price=" << _price << std::endl;
}
};
// 原型工厂(管理器)
class FruitPrototypeFactory
{
std::unordered_map<std::string, std::unique_ptr<Fruit>> prototypes;
public:
// 注册原型
void add(const std::string &key, std::unique_ptr<Fruit> prototype)
{
prototypes[key] = std::move(prototype);
}
// 根据键值克隆对象
std::unique_ptr<Fruit> create(const std::string &key) const
{
auto it = prototypes.find(key);
if (it != prototypes.end())
{
return it->second->clone();
}
return nullptr;
}
};
// 客户端
int main()
{
// 1. 创建工厂并注册原型
FruitPrototypeFactory factory;
factory.add("apple", std::make_unique<Apple>("RedApple", 0.3, 1.2));
factory.add("watermelon", std::make_unique<Watermelon>("SeedlessWatermelon", 4.0, 2.5));
// 2. 完全通过工厂创建对象,无需具体类名
auto fruit1 = factory.create("apple");
auto fruit2 = factory.create("apple");
auto fruit3 = factory.create("watermelon");
if (fruit1)
{
fruit1->show();
}
if (fruit2)
{
fruit2->show();
}
if (fruit3)
{
fruit3->show();
}
// 3. 动态添加新原型,无需修改已有代码
factory.add("GreenApple", std::make_unique<Apple>("GreenApple", 0.35, 1.5));
auto fruit4 = factory.create("GreenApple");
if (fruit4)
{
fruit4->show();
}
return 0;
}
结构型模式
适配器模式
适配器模式(Adapter Pattern):它通过引入一个中间层,将不兼容的接口协作起来,极大提升了代码的复用性和系统的灵活性。
适配器模式主要有两种形式:对象适配器 和类适配器。C++ 中通常优先使用对象适配器,但借助多重继承也可实现类适配器。
C++ 标准库中的适配器
- 容器适配器 :将已有的序列容器(vector/deque/list)适配 成受限的栈、队列、优先级队列接口,隐藏底层容器的全部操作方法,只暴露 push/pop/top 等。
std::stack<T, Container=deque<T>>std::queue<T, Container=deque<T>>std::priority_queue<T, Container=vector<T>, Compare=less<T>>
- 迭代器适配器
- 反向迭代器
std::reverse_iterator:将双向迭代器适配为反向遍历的迭代器。 - 插入迭代器
std::back_insert_iterator、std::front_insert_iterator、std::insert_iterator:将容器的元素赋值操作适配为插入操作。 - 移动迭代器
std::move_iterator:解引用时返回右值引用,适配移动语义。 - 函数适配器
std::bind:将函数参数绑定、重排、调整,适配成所需可调用类型。std::mem_fn:将成员函数指针适配为可调用对象,把对象作为首个参数。std::not_fn:将谓词结果取反。
对象适配器
c
#include <iostream>
#include <memory>
#include <string>
// 目标接口
class Logger
{
public:
virtual ~Logger() = default;
virtual void log(const std::string &message) = 0;
};
// 被适配者
class OldLogger
{
public:
void write_message(const char *msg)
{
std::cout << "[oldLogger] " << msg << std::endl;
}
};
// 对象适配器:持有 OldLogger,实现 Logger 接口
class OldLoggerAdapter : public Logger
{
private:
std::unique_ptr<OldLogger> _adaptee;
public:
OldLoggerAdapter(std::unique_ptr<OldLogger> oldLogger)
: _adaptee(std::move(oldLogger))
{
}
void log(const std::string &message) override
{
// 进行转换:string -> const char*
_adaptee->write_message(message.c_str());
}
};
// 客户端代码:只依赖 Logger 接口,使用引用传递(避免 unique_ptr 类型绑定问题)
void client_code(Logger &logger)
{
logger.log("Hello from client");
}
int main()
{
// 创建适配器
auto adapter = std::make_unique<OldLoggerAdapter>(std::make_unique<OldLogger>());
// 解引用 unique_ptr,传递 Logger& 给客户端
client_code(*adapter);
return 0;
}
类适配器
类适配器的缺点:
- 只能适配一个具体 Adaptee,无法动态替换 Adaptee 实例。
- 暴露了 Adaptee 的所有非私有成员(若公有继承则会破坏封装)。
- 仅当 Target 是纯虚接口时才有意义。
c
#include <iostream>
#include <memory>
#include <string>
// 目标接口(抽象类)
class Logger
{
public:
virtual void log(const std::string &text) = 0;
virtual ~Logger() = default;
};
// 被适配者
class OldLogger
{
public:
void output(const char *data, size_t length)
{
std::string content(data, length);
std::cout << "OldLogger printing: " << content << std::endl;
}
};
// 类适配器:同时继承接口和实现
class LoggerAdapter : public Logger, private OldLogger
{
public:
void log(const std::string &text) override
{
// 调用私有继承来的 OldLogger 方法
output(text.c_str(), text.size());
}
};
// 使用
int main()
{
LoggerAdapter adapter;
Logger *logger = &adapter;
logger->log("Success text");
return 0;
}
模板适配器(编译时多态)
C++ 泛型编程经常使用模板实现适配器,无需运行时开销。
c
#include <iostream>
#include <memory>
#include <string>
// 目标接口由模板参数隐式定义:需要 operator()
class TargetFunction
{
public:
virtual int calculate(int a, int b) = 0;
virtual ~TargetFunction() = default;
};
// 被适配函数:普通函数,参数顺序不同
int oldSubtract(int x, int y)
{
return x - y;
}
// 模板适配器:将任意可调用对象包装成 TargetFunction 接口
template <typename Partner>
class CallableAdapter : public TargetFunction
{
private:
Partner _partner; // 可调用对象,可以是函数指针、lambda 或者函数对象
public:
explicit CallableAdapter(Partner partner)
: _partner(std::move(partner))
{
}
int calculate(int a, int b) override
{
return _partner(a, b); // 实际调用可调用对象
}
};
// 使用
int main()
{
CallableAdapter adapter(oldSubtract);
TargetFunction *target = &adapter;
std::cout << target->calculate(5, 3);
return 0;
}
装饰器模式
装饰器模式(Decorator Pattern)是一种结构型设计模式,允许在不修改现有对象代码的前提下,通过将对象包装在装饰器类的对象中,动态地给对象添加新的职责或行为。该模式遵循开闭原则(对扩展开放,对修改关闭),提供了一种比继承更灵活的扩展方式。
当我们需要为某个对象增加额外功能时,通常会想到继承,但继承是静态的,会在编译时决定行为,而且可能导致子类数量爆炸。装饰器模式使用组合加委托,在运行时动态地为对象附加功能,且可以组合多个装饰器形成链式叠加效果。
标准的装饰器模式包含四个角色:
- 抽象组件(Component)
定义对象的接口,可以是抽象类或接口。具体组件和装饰器都实现该接口。 - 具体组件(Concrete Component)
实现了抽象组件接口,是被装饰的原始对象。 - 抽象装饰器(Decorator)
实现组件接口,并持有一个指向组件对象的引用(或指针)。它的接口实现通常会把请求转发给被装饰的组件,并可在转发前后添加额外行为。 - 具体装饰器(Concrete Decorator)
继承自抽象装饰器,负责具体附加功能的实现。
c
#include <iostream>
#include <string>
#include <memory>
// 抽象组件
class Drink
{
public:
virtual std::string getDescription() const = 0;
virtual double Price() const = 0;
virtual ~Drink() = default;
};
// 具体组件
class PearlMilkTea : public Drink
{
public:
std::string getDescription() const override
{
return "Pearl Milk Tea";
}
double Price() const override
{
return 9.00;
}
};
class LemonTea : public Drink
{
public:
std::string getDescription() const override
{
return "Lemon Tea";
}
double Price() const override
{
return 4.00;
}
};
// 抽象装饰器
class AbstractDecorator : public Drink
{
protected:
std::unique_ptr<Drink> _drink; // 拥有被装饰对象
public:
AbstractDecorator(std::unique_ptr<Drink> drink)
: _drink(std::move(drink))
{
}
};
// 具体装饰器
// 加冰装饰器
class Ice : public AbstractDecorator
{
public:
Ice(std::unique_ptr<Drink> drink)
: AbstractDecorator(std::move(drink))
{
}
std::string getDescription() const override
{
return _drink->getDescription() + ", Ice";
}
double Price() const override
{
return _drink->Price() + 0.05;
}
};
// 加糖装饰器
class Sugar : public AbstractDecorator
{
public:
Sugar(std::unique_ptr<Drink> drink)
: AbstractDecorator(std::move(drink))
{
}
std::string getDescription() const override
{
return _drink->getDescription() + ", Sugar";
}
double Price() const override
{
return _drink->Price() + 0.10;
}
};
int main()
{
// 构建一杯加双份摩卡和牛奶的浓缩咖啡
std::unique_ptr<Drink> drink = std::make_unique<PearlMilkTea>();
drink = std::make_unique<Ice>(std::move(drink));
drink = std::make_unique<Sugar>(std::move(drink));
drink = std::make_unique<Ice>(std::move(drink));
std::cout << drink->getDescription() << ". Total=" << drink->Price() << "yuan" << std::endl;
return 0;
}
代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,它为另一个对象提供一个替身或占位符,以便控制对该对象的访问。代理对象在客户端和目标对象之间充当中介,可以在不改变目标对象的前提下,添加额外的控制逻辑,例如延迟初始化、访问控制、日志记录、缓存等。
代理模式的参与者通常包含以下角色:
- Subject(抽象主题):定义了 RealSubject 和 Proxy 的共同接口,这样 Proxy 可以在任何需要 RealSubject 的地方使用。
- RealSubject(真实主题):定义了代理所代表的真实对象,包含核心业务逻辑。
- Proxy(代理):保存一个引用,使得代理可以访问真实主题;提供与 Subject 相同的接口,以便替代真实主题;控制对真实主题的访问,并可能负责创建和删除真实主题。
代理模式分类
- 远程代理(Remote Proxy):为位于不同地址空间的对象提供本地代表,如 RPC 存根。
- 虚拟代理(Virtual Proxy):延迟创建开销大的对象,直到真正需要时才实例化。
- 保护代理(Protection Proxy):控制对原始对象的访问,依据调用者的权限决定是否允许访问。
- 智能引用代理(Smart Reference Proxy):在访问对象时执行额外的动作,如引用计数、线程安全检查、日志记录等。
- 缓存代理(Cache Proxy):为昂贵的操作结果提供临时存储,多次相同请求可共享结果。
- 日志代理(Logging Proxy):记录对对象的每一次访问请求。
- 同步代理(Synchronization Proxy):为多线程环境下的对象访问提供同步控制。
简单实现
c
#include <iostream>
#include <memory>
// 抽象主题
class AbstractTheme
{
public:
virtual void request() = 0;
virtual ~AbstractTheme() = default;
};
// 真实主题
class RealTheme : public AbstractTheme
{
public:
void request() override
{
std::cout << "RealTheme: Handling request." << std::endl;
}
};
// 基础代理
class Proxy : public AbstractTheme
{
private:
std::unique_ptr<RealTheme> _realTheme; // 延迟持有
public:
void request() override
{
// 可在此处执行额外逻辑(访问控制、日志等)
if (!_realTheme)
{
_realTheme = std::make_unique<RealTheme>(); // 延迟创建
}
std::cout << "Proxy: Logging before request." << std::endl;
_realTheme->request();
std::cout << "Proxy: Logging after request." << std::endl;
}
};
// 客户端代码
void clientCode(AbstractTheme &theme)
{
theme.request();
}
int main()
{
Proxy proxy;
clientCode(proxy);
return 0;
}
虚拟代理
代理模式的细分类太多,只实现简单的虚拟代理。
虚拟代理--延迟加载真实图像
c
#include <iostream>
#include <memory>
#include <string>
// 抽象图像接口
class Image
{
public:
virtual void display() = 0;
virtual ~Image() = default;
};
// 真实图像:从磁盘加载
class RealImage : public Image
{
private:
std::string _filename;
void loadFromDisk()
{
std::cout << "Loading " << _filename << " from disk..." << std::endl;
}
public:
RealImage(const std::string &file)
: _filename(file)
{
loadFromDisk();
}
void display() override
{
std::cout << "Displaying " << _filename << std::endl;
}
};
// 虚拟代理:延迟加载真实图像
class ImageProxy : public Image
{
private:
std::string _filename;
std::unique_ptr<RealImage> _realImage;
public:
ImageProxy(const std::string &file)
: _filename(file)
{
}
void display() override
{
if (!_realImage)
{
_realImage = std::make_unique<RealImage>(_filename);
}
_realImage->display();
}
};
int main()
{
// 创建代理时并不加载真实图像
ImageProxy image("photo.jpg");
std::cout << "Proxy created, image not loaded." << std::endl;
// 第一次 display 才触发加载
image.display();
// 第二次 display 使用已缓存的真实对象
image.display();
return 0;
}
外观模式
外观模式(Facade Pattern):为一个复杂的子系统提供一个统一的高层接口,使得子系统更容易使用。外观模式通过创建一个外观类(Facade),将客户端与多个子系统的内部复杂性隔离开。客户端只需与外观类交互,而不需要直接了解子系统的具体实现细节。
外观模式主要包含两个构成:
- Facade(外观类):知道哪些子系统负责处理请求,将客户端的请求委派给适当的子系统对象。
- Subsystem classes(子系统类):实现子系统的具体功能,处理 Facade 委派的任务。子系统类本身并不知道 Facade 的存在,它们之间也没有直接关联。
客户端(Client)通过调用 Facade 提供的方法来完成操作,不直接调用子系统。
网站用户注册流程
用户填写表单并点击注册后,后端可能需要:验证输入格式、检查用户名是否重复、在用户表中插入记录、创建默认个人资料页、发送验证邮件、记录审计日志。Facade.register(username, password) 可以封装这一连串子系统调用。
c
#include <iostream>
#include <string>
// 子系统类
// 输入验证器
class InputValidator
{
public:
bool validate(const std::string &username, const std::string &password)
{
if (username.length() < 3)
{
std::cout << "error.username is too short" << std::endl;
return false;
}
if (password.length() < 6)
{
std::cout << "error.password is too short" << std::endl;
return false;
}
std::cout << "input format is valid" << std::endl;
return true;
}
};
// 用户仓库
class UserRepository
{
public:
bool isUsernameTaken(const std::string &username)
{
// 模拟查询数据库,假设 "admin" 已被占用
if (username == "admin")
{
std::cout << "error.username is already taken" << std::endl;
return true;
}
std::cout << "input format is valid" << std::endl;
return false;
}
void insertUser(const std::string &username, const std::string &password)
{
std::cout << "insertUser: " << username << "\n";
}
};
// 个人资料创建器
class ProfileCreator
{
public:
void createDefaultProfile(const std::string &username)
{
std::cout << "createDefaultProfile: " << username << "\n";
}
};
// 邮件服务
class EmailService
{
public:
void sendVerificationEmail(const std::string &username)
{
std::cout << "sendVerificationEmail: " << username << "\n";
}
};
// 审计日志记录器
class AuditLogger
{
public:
void log(const std::string &message)
{
std::cout << "audit: " << message << "\n";
}
};
// 外观类
class Facade
{
private:
InputValidator _validator;
UserRepository _userRepo;
ProfileCreator _profile;
EmailService _email;
AuditLogger _audit;
public:
bool registerUser(const std::string &username, const std::string &password)
{
std::cout << "\nuser registration started\n";
// 1. 校验输入格式
if (!_validator.validate(username, password))
{
_audit.log("registration failed: input validation failed, username=" + username);
std::cout << "registration cancelled\n\n";
return false;
}
// 2. 检查用户名唯一性
if (_userRepo.isUsernameTaken(username))
{
_audit.log("registration failed: username conflict, username=" + username);
std::cout << "registration cancelled\n\n";
return false;
}
// 3. 插入用户表
_userRepo.insertUser(username, password);
// 4. 创建默认个人资料
_profile.createDefaultProfile(username);
// 5. 发送验证邮件
_email.sendVerificationEmail(username);
// 6. 审计日志
_audit.log("new user registered successfully: " + username);
std::cout << "registration successful\n\n";
return true;
}
};
// 客户端
int main()
{
Facade facade;
// 第一次尝试:用户名冲突
facade.registerUser("admin", "123456");
// 第二次尝试:密码太短
facade.registerUser("alice", "abc");
// 第三次尝试:成功注册
facade.registerUser("alice", "securePass");
return 0;
}
行为型模式
观察者模式
观察者模式(Observer Pattern):定义了对象间的一种一对多的依赖关系,使得每当一个对象(被观察者/主题)改变状态时,所有依赖它的对象(观察者)都会得到通知并自动更新。在 C++ 中,这个模式可以通过继承、接口、函数对象甚至模板等多种方式实现。