C++设计模式
面对对象中的设计原则
SOLID原则
SOLID 是面向对象设计的五个核心原则的首字母缩写。每个原则都是指导良好设计的重要指南。
**单一职责原则 (Single Responsibility Principle, SRP)**:
一个类应该只有一个明确的职责。换句话说,一个类应该只有一个导致其变化的原因。这有助于减少类的复杂性,并提高代码的可维护性和测试性。**开放封闭原则 (Open/Closed Principle, OCP)**:
软件实体(如类、模块、函数等)应该对扩展开放,对修改封闭。这意味着代码应该可以通过添加新功能进行扩展,而不需要修改现有代码,从而减少了因代码变更引发的错误风险。**里氏替换原则 (Liskov Substitution Principle, LSP)**:
子类应该可以替换其基类而不破坏程序的正确性。这确保了继承关系的合理性,避免了因为继承层次结构的不一致而导致的错误。
即:
子类可以扩展父类的功能,但不能改变父类原有的功能。
**接口隔离原则 (Interface Segregation Principle, ISP)**:
客户端不应该被迫实现它们不使用的接口。应当为特定的功能定义更小的接口,而不是一个包含过多方法的庞大接口。这减少了不必要的耦合,并提高了接口的可用性。
即:
使用多个专门的接口,而不使用单一的总接口
依赖倒置原则 (Dependency Inversion Principle, DIP):
高层模块不应该依赖于低层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。这强调了通过接口或抽象类建立模块之间的联系,而不是直接依赖于具体实现。- 传统的自顶向下设计
- 依赖倒置原则
- 传统的自顶向下设计
其他设计原则
除了 SOLID 原则,还有其他一些重要的面向对象设计原则:
**迪米特法则 (Law of Demeter, LoD)**:
一个对象应该对其他对象有尽可能少的了解。即,避免过多的直接对象关系,减少对象之间的耦合。**组合优于继承 (Composition over Inheritance)**:
在设计系统时,优先考虑使用组合来扩展功能,而不是过度依赖继承。组合可以提供更大的灵活性和更低的耦合度。**防御式编程 (Defensive Programming)**:
编写代码时,考虑潜在的异常情况,并确保代码在异常情况下不会崩溃。这有助于提高代码的鲁棒性和稳定性。
设计模式
创建型模式 ,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式 ,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式 ,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
单例模式
class Singleton{
public:
// 获取单例实例的静态成员函数
static Singleton& getInstance(){
static Singleton inst;
return inst;
}
// 删除拷贝构造函数和赋值运算符,防止复制和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
// 私有化构造函数,防止外部创建实例
Singleton() {}
// 私有化析构函数,确保单例对象在程序结束时被正确销毁
~Singleton() {}
}
工厂模式
简单工厂
在不暴露对象创建细节的情况下,根据输入参数创建对象
缺点:
- 创建新的产品需要修改工厂方法,不符合开放封闭原则
// 产品基类
class Product {
public:
virtual void use() = 0; // 虚函数
};
// 具体产品 A
class ProductA : public Product {
public:
void use() override {
std::cout << "Using Product A\n";
}
};
// 具体产品 B
class ProductB : public Product {
public:
void use() override {
std::cout << "Using Product B\n";
}
};
// 简单工厂类
class SimpleFactory {
public:
std::unique_ptr<Product> createProduct(const std::string& type) {
if (type == "A") {
return std::make_unique<ProductA>();
} else if (type == "B") {
return std::make_unique<ProductB>();
} else {
return nullptr;
}
}
};
int main() {
SimpleFactory factory;
auto productA = factory.createProduct("A");
if (productA) {
productA->use(); // 输出:Using Product A
}
auto productB = factory.createProduct("B");
if (productB) {
productB->use(); // 输出:Using Product B
}
return 0;
}
工厂方法
实现对象的延迟创建或动态选择,具体工厂中可以做别的操作
2抽象2具体,新增产品与工厂不需要改变代码,解决了简单工厂开闭原则的问题
// 产品基类
class Product {
public:
virtual void use() = 0; // 虚函数
};
// 具体产品 A
class ProductA : public Product {
public:
void use() override {
std::cout << "Using Product A\n";
}
};
// 具体产品 B
class ProductB : public Product {
public:
void use() override {
std::cout << "Using Product B\n";
}
};
// 工厂方法接口
class Factory {
public:
virtual std::unique_ptr<Product> createProduct() = 0; // 工厂方法
};
// 具体工厂 A
class FactoryA : public Factory {
public:
std::unique_ptr<Product> createProduct() override {
return std::make_unique<ProductA>();
}
};
// 具体工厂 B
class FactoryB : public Factory {
public:
std::unique_ptr<Product> createProduct() override {
return std::make_unique<ProductB>();
}
};
int main() {
FactoryA factoryA;
auto productA = factoryA.createProduct();
productA->use(); // 输出:Using Product A
FactoryB factoryB;
auto productB = factoryB.createProduct();
productB->use(); // 输出:Using Product B
return 0;
}
抽象工厂
当系统需要创建一组相关或相互依赖的对象时,确保它们的一致性和正确性,避免直接依赖于具体类
抽象工厂的结构(2抽象2具体,基于工厂方法增加了组合复杂度):
- 抽象工厂接口:定义一系列用于创建产品的方法,每个方法对应一个产品类型。
- 具体工厂类:实现抽象工厂接口,具体负责创建特定类型的产品。
- 抽象产品接口:定义每个产品的接口。
- 具体产品类:实现抽象产品接口,提供具体产品的功能。
// 抽象产品接口
class Chair {
public:
virtual void sit() = 0;
};
class Table {
public:
virtual void use() = 0;
};
// 具体产品
class ModernChair : public Chair {
public:
void sit() override {
std::cout << "Sitting on a modern chair.\n";
}
};
class VictorianChair : public Chair {
public:
void sit() override {
std::cout << "Sitting on a Victorian chair.\n";
}
};
class ModernTable : public Table {
public:
void use() override {
std::cout << "Using a modern table.\n";
}
};
class VictorianTable : public Table {
public:
void use() override {
std::cout << "Using a Victorian table.\n";
}
};
// 抽象工厂接口
class FurnitureFactory {
public:
virtual std::unique_ptr<Chair> createChair() = 0;
virtual std::unique_ptr<Table> createTable() = 0;
};
// 具体工厂
class ModernFurnitureFactory : public FurnitureFactory {
public:
std::unique_ptr<Chair> createChair() override {
return std::make_unique<ModernChair>();
}
std::unique_ptr<Table> createTable() override {
return std::make_unique<ModernTable>();
}
};
class VictorianFurnitureFactory : public FurnitureFactory {
public:
std::unique_ptr<Chair> createChair() override {
return std::make_unique<VictorianChair>();
}
std::unique_ptr<Table> createTable() override {
return std::make_unique<VictorianTable>();
}
};
int main() {
std::unique_ptr<FurnitureFactory> factory;
factory = std::make_unique<ModernFurnitureFactory>();
auto chair = factory->createChair();
auto table = factory->createTable();
chair->sit(); // 输出:Sitting on a modern chair.
table->use(); // 输出:Using a modern table.
factory = std::make_unique<VictorianFurnitureFactory>();
chair = factory->createChair();
table = factory->createTable();
chair->sit(); // 输出:Sitting on a Victorian chair.
table->use(); // 输出:Using a Victorian table.
return 0;
}
工厂方法与抽象工厂主要区别
工厂方法模式 主要用于创建单一类型的产品,通常是通过继承让子类来决定具体创建的产品类型。
抽象工厂模式 用于创建一系列相关或相互依赖的产品,提供多个工厂方法来创建不同类型的产品。具体工厂通过实现抽象工厂接口,提供具体的创建逻辑。
适配器模式
将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。
如何解决:继承
或组合
(推荐)。
注意事项:过多地使用适配器,会让系统非常零乱,不易整体进行把握。适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
#include <iostream>
#include <string>
// 目标接口
class Target {
public:
virtual void request() = 0;
};
// 被适配接口
class Adaptee {
public:
void specificRequest() {
std::cout << "Adaptee specific request" << std::endl;
}
};
// 适配器
class Adapter : public Target {
public:
Adapter(Adaptee *adaptee) : adaptee(adaptee) {}
void request() override {
adaptee->specificRequest(); // 转换接口
}
private:
Adaptee *adaptee; // 组合
};
// 客户端
int main() {
Adaptee adaptee;
Adapter adapter(&adaptee); // 使用适配器
adapter.request(); // 通过目标接口调用
return 0;
}
观察者模式
观察者模式(Observer Pattern) 是一种行为型设计模式,允许对象之间建立一种一对多的依赖关系。当一个对象的状态发生变化时,所有依赖它的对象都会收到通知并自动更新。这个模式通常用于事件驱动的系统、数据绑定、发布订阅机制等。
观察者模式的关键角色:
主题(Subject):也称为发布者(Publisher),它维护了一组观察者,并提供注册、注销观察者的方法,以及通知所有观察者的方法。
观察者(Observer):也称为订阅者(Subscriber),它定义了接收通知的方法。观察者通过注册到主题,接收主题的状态变化通知。
通知方法:在观察者模式中,通常使用一个方法来通知所有观察者状态的变化。
观察者模式的应用场景:
- 当一个对象的状态变化需要通知其他对象时。
- 当希望松散耦合的设计,让对象之间的依赖关系最小化时。
- 当需要一种广播机制,允许对象间的事件传播时。
// 抽象观察者
class Observer {
public:
virtual void update(float temperature) = 0; // 接收更新的方法
virtual ~Observer() = default;
};
// 主题类
class Subject {
public:
void addObserver(std::shared_ptr<Observer> observer) {
observers.push_back(observer); // 注册观察者
}
void removeObserver(std::shared_ptr<Observer> observer) {
observers.erase(
std::remove(observers.begin(), observers.end(), observer),
observers.end()); // 注销观察者
}
void notifyObservers(float temperature) {
for (auto& observer : observers) {
observer->update(temperature); // 通知所有观察者
}
}
private:
std::vector<std::shared_ptr<Observer>> observers; // 观察者列表
};
// 具体观察者
class Display : public Observer {
public:
void update(float temperature) override { // 实现更新方法
std::cout << "Temperature updated: " << temperature << "°C\n";
}
};
// 主程序
int main() {
// 创建主题和观察者
auto weatherStation = std::make_shared<Subject>();
auto display1 = std::make_shared<Display>();
auto display2 = std::make_shared<Display>();
// 注册观察者
weatherStation->addObserver(display1);
weatherStation->addObserver(display2);
// 通知观察者
weatherStation->notifyObservers(25.0f); // 所有观察者收到更新
// 注销一个观察者
weatherStation->removeObserver(display2);
// 再次通知观察者
weatherStation->notifyObservers(26.0f); // 只有一个观察者收到更新
return 0;
}
使用回调函数也是实现观察者模式的一种简洁而直接的方式
观察者模式与发布/订阅模式
有读者可能认为MQTT协议的运行机制就是观察者模式,其实不然。
观察者模式 是一种行为型设计模式,定义了对象之间的一对多关系。在这个模式中,”主题”(Subject)负责维护一个观察者列表,并在状态改变时通知所有观察者。观察者只关心从主题接收通知。
发布/订阅模式(Publish/Subscribe)允许发布者向一个主题发布消息,订阅者可以订阅这个主题并接收相关消息。与观察者模式不同的是,发布者和订阅者之间没有直接的耦合。它们通过中间层(如 MQTT 的 “Broker”)进行通信。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1430797759@qq.com