目录
一、设计模式是什么?
设计模式是指在软件开发中,一种经过验证的,解决常见设计问题的抽象模板。只需要修改少量的代码,就可以适应需求的变化。
二、设计模式基础
2.1.面向对象的思想
面向对象编程的四大特性:封装、继承、多态和抽象。
Ⅰ.封装
封装是指将数据和操作数据的方法封装在一个类中,使其成为一个独立的单元。通过封装,可以隐藏数据的具体实现细节,只暴露出必要的接口,提高代码的安全性和可维护性。
Ⅱ.继承
继承是指一个类(子类)可以使用另一个类(父类)的属性和方法,同时可以在其基础上添加或修改功能。通过继承,可以实现代码重用和层次化建模,减少重复编写代码的工作量。
Ⅲ.多态
多态是指允许不同的对象以相同的接口执行不同的操作。多态性有助于实现代码的灵活性、可维护性和可扩展性,同时提高了代码的可读性和可重用性。具体而言,一个基类的成员函数设置为虚函数后,其子类便可对该虚函数进行重写,即函数名相同,但参数列表和函数功能不一样。
Ⅳ.抽象
一个类中至少有一个纯虚函数,则被成为抽象类。抽象类不可以实例化,没有具体的实现,其作用是定义接口和规范,确保子类实现特定的方法,以提高代码的一致性和可维护性。
2.2.设计原则
- 单一职责:一个类应该只有一个引起变化的原因,即一个类应该只有一个职责;
- 开放/封闭原则:软件实体(类、模块、函数等)应该对扩展开发,对修改关闭;
- 里氏替换原则:任何可以使用父类实例的地方,都应该能够使用子类实例,而且不会导致程序的错误行为。
- 接口隔离原则:尽量将庞大的接口拆分为更小的、更具体的接口。不应该强迫一个类实现它用不到的接口。
- 依赖倒置原则:高层模块不应该依赖于低层模块,它们都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
-
最少知道原则:一个对象应该只和它的朋友进行交互,不要给其他对象暴露过多的信息。
三、常用的设计模式
3.1.模板方法
定义:定义一个操作中的算法骨架,而将一些步骤延迟到子类。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
背景:某个动物园有一套固定的表演流程,但是其中有若干个表演子流程可创新替换,以尝试迭代更新表演流程。
解决的问题:稳定点:算法骨架
变化点:子流程需要变化
代码结构:
- 基类中有骨架流程接口;
- 所有子流程对子类开发并且是虚函数;
- 多态使用方式。
本质:通过固定算法骨架来约束子类的行为。
使用场景:
- 固定算法框架,变动细节:当算法步骤固定但某些细节由子类实现。
代码示例:
#include <iostream>
class Zoo {
public:
void performShow() {
prepareAnimals();
performTrick1();
performTrick2();
cleanUp();
}
protected:
virtual void prepareAnimals() {
std::cout << "准备动物" << std::endl;
}
virtual void performTrick1() {
std::cout << "表演技巧1" << std::endl;
}
virtual void performTrick2() {
std::cout << "表演技巧2" << std::endl;
}
virtual void cleanUp() {
std::cout << "清理现场" << std::endl;
}
};
class UpdatedZoo : public Zoo {
protected:
virtual void performTrick2() {
std::cout << "更新的表演技巧2" << std::endl;
}
};
int main() {
Zoo zoo;
zoo.performShow(); // 输出:准备动物 -> 表演技巧1 -> 表演技巧2 -> 清理现场
std::cout << std::endl;
UpdatedZoo updatedZoo;
updatedZoo.performShow(); // 输出:准备动物 -> 表演技巧1 -> 更新的表演技巧2 -> 清理现场
return 0;
}
说明:
在上面的示例中,Zoo
类是一个动物园的基类,定义了一个固定的表演流程。performShow
方法是一个模板方法,它定义了整个表演的流程,其中包含了四个步骤:准备动物、表演技巧1、表演技巧2和清理现场。
prepareAnimals
、performTrick1
、performTrick2
和cleanUp
都是虚函数,可以在子类中进行重写,以实现不同的表演子流程。
UpdatedZoo
是Zoo
的一个子类,它重写了performTrick2
方法,以替换原来的表演技巧2。
在main
函数中,我们创建了一个Zoo
对象和一个UpdatedZoo
对象,并调用它们的performShow
方法进行表演,输出结果展示了不同的表演流程。
3.2.观察者模式
定义:定义对象间的一种一对多(变化)的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
背景:气象站发布气象资料给数据中心,数据中心经过处理,将气象信息更新到两个不同的显示终端(A和B)。
解决的问题:稳定点:“一”对“多”的依赖关系,“一”变化“多”跟着变化。
变化点:“多”增加/减少
本质:触发联动
使用场景:
- 一对多通知:一个对象状态改变时,自动通知多个依赖对象。
代码示例:
#include <iostream>
#include <vector>
#include <string>
//1. 定义观察者接口
class Observer {
public:
virtual void update(const std::string &weatherData) = 0;
};
//2. 定义被观察者接口
class Subject {
public:
virtual void attach(Observer *observer) = 0;
virtual void detach(Observer *observer) = 0;
virtual void notify() = 0;
};
//实现具体的气象站(被观察者)
class WeatherStation : public Subject {
private:
std::vector<Observer *> observers;
std::string weatherData;
public:
void attach(Observer *observer) override {
observers.push_back(observer);
}
void detach(Observer *observer) override {
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
}
void notify() override {
for (Observer *observer : observers) {
observer->update(weatherData);
}
}
void setWeatherData(const std::string &newData) {
weatherData = newData;
notify();
}
};
//实现具体的显示终端(观察者)
class DisplayTerminal : public Observer {
private:
std::string name;
public:
DisplayTerminal(const std::string &name) : name(name) {}
void update(const std::string &newData) override {
std::cout << "Display Terminal " << name << " showing weather data: " << newData << std::endl;
}
};
//使用示例
int main() {
WeatherStation weatherStation;
DisplayTerminal terminalA("A");
DisplayTerminal terminalB("B");
weatherStation.attach(&terminalA);
weatherStation.attach(&terminalB);
weatherStation.setWeatherData("Sunny, 25°C");
weatherStation.setWeatherData("Rainy, 18°C");
return 0;
}
说明:
Observer
:定义了观察者接口,要求实现update
方法。Subject
:定义了被观察者接口,提供了attach
、detach
和notify
方法。WeatherStation
:实现了Subject
接口,管理观察者列表,并在数据更新时通知所有观察者。DisplayTerminal
:实现了Observer
接口,提供了更新和显示方法。- 在
main
函数中,创建了一个WeatherStation
对象和两个DisplayTerminal
对象,并将显示终端注册到气象站,模拟了气象数据的更新和通知过程。
代码实现了经典的观察者模式,当 WeatherStation
的天气数据更新时,所有注册的 DisplayTerminal
观察者都会接收到通知并更新显示的数据。
3.3.策略模式
定义:策略模式是一种行为设计模式,它定义了一系列算法,将每一个算法封装起来,并使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。
背景:某商场节假日有固定促销活动,为了加大促销力度,先提升国庆节促销活动规格。
解决的问题:稳定点:客户程序与算法的调用关系
变化点:算法变化
本质:分离算法,选择实现
使用场景:
- 可替换算法或行为:需要在运行时选择不同的算法或行为
代码示例:
#include <iostream>
#include <string>
// 策略接口
class PromotionStrategy {
public:
virtual ~PromotionStrategy() {}
virtual void applyPromotion() const = 0;
};
// 具体策略A: 普通节假日促销
class HolidayPromotion : public PromotionStrategy {
public:
void applyPromotion() const override {
std::cout << "Applying standard holiday promotion with 10% discount." << std::endl;
}
};
// 具体策略B: 国庆节促销
class NationalDayPromotion : public PromotionStrategy {
public:
void applyPromotion() const override {
std::cout << "Applying National Day promotion with 20% discount and a free gift." << std::endl;
}
};
// 上下文类
class PromotionContext {
private:
PromotionStrategy* strategy;
public:
PromotionContext(PromotionStrategy* strategy) : strategy(strategy) {}
~PromotionContext() { delete strategy; }
void setStrategy(PromotionStrategy* newStrategy) {
delete strategy;
strategy = newStrategy;
}
void applyPromotion() const {
strategy->applyPromotion();
}
};
// 使用示例
int main() {
// 使用普通节假日促销策略
PromotionContext context(new HolidayPromotion());
context.applyPromotion();
// 切换到国庆节促销策略
context.setStrategy(new NationalDayPromotion());
context.applyPromotion();
return 0;
}
说明:
- 策略接口
PromotionStrategy
定义了applyPromotion
方法,所有具体的促销策略都必须实现这个方法。 - 具体策略
HolidayPromotion
和NationalDayPromotion
分别实现了普通节假日促销和国庆节促销的逻辑。 - 上下文类
PromotionContext
持有一个PromotionStrategy
对象的指针,并可以在运行时切换策略。它通过调用策略的applyPromotion
方法来应用促销策略。
这个设计使得促销策略可以灵活地在运行时切换,而不需要修改 PromotionContext
的代码,实现了策略模式的核心优势。
3.4.单例模式
定义:单例模式是一种创建型设计模式,旨在确保一个类只有一个实例,并提供一个全局访问点来获取该实例。这通常用于需要控制全局访问的资源,例如配置管理器、线程池、数据库连接池等。
本质:
单例模式的本质是控制实例化过程,以确保在应用程序中只存在一个该类的实例。它通常通过以下几个步骤来实现:
- 私有化构造函数:阻止外部代码通过构造函数创建多个实例。
- 静态成员变量:用于存储唯一的实例。
- 静态方法:提供获取唯一实例的全局访问点。
- 线程安全:在多线程环境下,需要确保实例的创建是线程安全的。
使用场景:
-
数据库连接池:一个应用程序可能需要多个数据库连接,但所有连接都应该从一个连接池中获取,避免了每次都建立新的连接。单例模式可以确保只有一个连接池实例存在。
-
配置管理器:应用程序的配置通常在启动时读取,并在整个生命周期中保持不变。使用单例模式可以确保配置管理器只有一个实例,避免了重复读取配置文件的开销。
-
日志记录器:在整个应用程序中,只需一个日志记录器实例来处理所有的日志信息。使用单例模式可以确保所有日志记录请求都由同一个日志记录器处理,保持日志的一致性和完整性。
代码示例:
#include <iostream>
#include <mutex>
// 单例类
class Singleton {
private:
// 私有构造函数,防止外部实例化
Singleton() {
std::cout << "Singleton instance created." << std::endl;
}
// 删除拷贝构造函数和赋值运算符,防止拷贝
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 静态指针,存储唯一的实例
static Singleton* instance;
// 互斥量,确保线程安全
static std::mutex mtx;
public:
// 获取唯一实例的静态方法
static Singleton* getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
// 示例方法
void showMessage() const {
std::cout << "Hello from Singleton!" << std::endl;
}
};
// 初始化静态成员
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
// 使用示例
int main() {
// 获取单例实例
Singleton* singleton1 = Singleton::getInstance();
singleton1->showMessage();
// 获取另一个单例实例
Singleton* singleton2 = Singleton::getInstance();
singleton2->showMessage();
// 验证两个实例是相同的
if (singleton1 == singleton2) {
std::cout << "Both instances are the same." << std::endl;
}
return 0;
}
说明:
- 私有构造函数:
Singleton()
被声明为私有,防止外部直接创建实例。 - 删除拷贝构造函数和赋值运算符:
Singleton(const Singleton&)
和operator=
被删除,防止复制。 - 静态成员变量:
static Singleton* instance
用于存储唯一实例。 - 静态方法:
getInstance()
提供全局访问点,并在需要时创建实例。使用双重检查锁定(Double-Check Locking)模式来确保线程安全。 - 线程安全:使用
std::mutex
确保在多线程环境中对实例的创建是安全的。
这个实现确保了在整个应用程序中只有一个 Singleton
实例,并且支持多线程环境下的安全访问。
3.5.工厂模式
定义:工厂模式是一种创建型设计模式,它定义了一个用于创建对象的接口,让子类决定实例化哪一个具体类。工厂模式使得类的实例化延迟到子类中进行,从而实现了对象创建的解耦。
背景:在软件开发中,随着系统的复杂性增加,常常需要创建大量的对象。这些对象可能有不同的实现,但在客户端代码中,它们的创建方式应该被隐藏。工厂模式允许将对象的创建过程集中在一个工厂类中,并将创建细节封装起来,从而简化了对象的创建和管理过程。
解决的问题:稳定点:创建同类对象的接口
变化点:创建对象的扩展
本质:工厂模式的本质是将对象创建的职责从客户端代码中抽离出来,交由工厂类负责。这种设计使得对象的创建和使用分离,提高了系统的灵活性和可扩展性。
使用场景:
- 创建对象时不确定具体类:当需要创建的对象的类型在运行时才能确定时。
- 需要一个工厂类来管理对象创建:当对象创建复杂且需要集中管理时。
- 避免直接使用构造函数:当使用构造函数可能导致代码难以维护或扩展时。
代码示例:
#include <iostream>
#include <memory>
#include <string>
// 产品接口
class Product {
public:
virtual ~Product() {}
virtual void use() const = 0;
};
// 具体产品A
class ConcreteProductA : public Product {
public:
void use() const override {
std::cout << "Using ConcreteProductA" << std::endl;
}
};
// 具体产品B
class ConcreteProductB : public Product {
public:
void use() const override {
std::cout << "Using ConcreteProductB" << std::endl;
}
};
// 工厂接口
class Factory {
public:
virtual ~Factory() {}
virtual std::unique_ptr<Product> createProduct() const = 0;
};
// 具体工厂A
class ConcreteFactoryA : public Factory {
public:
std::unique_ptr<Product> createProduct() const override {
return std::make_unique<ConcreteProductA>();
}
};
// 具体工厂B
class ConcreteFactoryB : public Factory {
public:
std::unique_ptr<Product> createProduct() const override {
return std::make_unique<ConcreteProductB>();
}
};
// 使用示例
int main() {
// 创建工厂A和工厂B
std::unique_ptr<Factory> factoryA = std::make_unique<ConcreteFactoryA>();
std::unique_ptr<Factory> factoryB = std::make_unique<ConcreteFactoryB>();
// 通过工厂创建产品
std::unique_ptr<Product> productA = factoryA->createProduct();
std::unique_ptr<Product> productB = factoryB->createProduct();
// 使用产品
productA->use();
productB->use();
return 0;
}
说明:
- 产品接口
Product
:定义了所有具体产品的公共接口。 - 具体产品
ConcreteProductA
和ConcreteProductB
:实现了Product
接口,提供具体的产品实现。 - 工厂接口
Factory
:定义了创建产品的公共接口。 - 具体工厂
ConcreteFactoryA
和ConcreteFactoryB
:实现了Factory
接口,负责创建具体的产品对象。 - 客户端代码:通过工厂创建具体产品并使用它们。客户端代码只依赖于工厂接口,不需要知道具体的产品类。