设计模式概述及五个常见的设计模式详解(C++用例)
概述
设计模式
1994年,Erich Gamma等四人(Gang of Four)出版了Design Patterns - Elements of Reusable Object-Oriented Software即《设计模式-可复用的面向对象软件元素》,该书中首次提到了软件开发中的设计模式理念。他们提出的设计模式主要基于这样的面向对象设计元素:
- 对接口编程而不是对实现编程
- 优先使用对象组合而不是继承
在这本书中,他们提出了23种设计模式,它们被广泛认为是解决面向对象设计种常见问题的经典方案,这些方案被分为三大类:创建型、结构型和行为型。
我对设计模式的理解
首先,设计模式是一种思想,这种思想不仅仅适用于程序设计。这样的思想来源于生活,被用于程序设计,这也意味着它一样适用于现实生活中的逻辑。也许我们在编程和生活中并不会刻意的提到某种模式,但这并不意味着我们不会用到某种设计模式的思想,学习设计模式可以让我们规避更多弯路,提高设计效率,让程序设计过程模板化、规范化。
此外,由于它是一种抽象思维的归纳和概括,我们绝不应按照书面的文字去理解设计模式,而应当结合实例,不论是生活上的还是程序上的。文字是死板的,但思想是灵活的,由此我们并不需要刻意而严肃地去背诵设计模式。在学习之处,我们可以略微刻板些去学习几个常见的设计模式概念,然后再到实际程序设计中去发现它们,进而不断提高自己的程序设计能力和思维水准。
最后,对于程序设计中的设计模式,它是面向对象的思想,应当首先建立在面向对象思维的基础上。基于某个编程语言,比如C++的实例去理解设计模式时,应当首先理解C++的面向对象相关理念。
单例模式
单例模式(Singleton Pattern)的主要目标是确保某个类只有一个实例存在,并提供一个访问该实例的全局访问点。这意味着,无论何时何地,只要通过这个访问点获取到的都是同一个实例。单例模式在需要频繁访问共享资源或控制实例数量的场景中非常有用。
优缺点
优点
- 控制实例数量:确保一个类只有一个实例,节省资源;
- 全局访问点:提供全局访问点,方便管理和访问共享资源;
- 延迟加载:懒汉式单例可以延迟加载,只有在需要时才创建实例。
缺点
- 不利于扩展:单例模式使类变得难以扩展,因为它限制了继承和多态性;
- 隐藏依赖关系:全局访问点可能会隐藏类之间的依赖关系,导致代码难以理解和维护;
- 并发问题:多线程环境下需要处理线程安全问题,增加了实现复杂性。
应用场景
- 资源管理:如数据库连接池、线程池、日志管理器等;
- 配置管理:全局配置类,保证应用程序中的配置一致;
- 设备管理:如打印机管理类,确保只有一个实例与物理设备交互。
实现方法
这里列举四种常见的实现方式。
1.懒汉式单例(Lazy Initialization)
懒汉式单例在第一次使用时进行实例化,确保只有在需要时才创建对象。
class Singleton {
private:
static Singleton* instance;
Singleton() {} // 私有构造函数
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
// 禁止拷贝和赋值操作
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
// 初始化静态成员
Singleton* Singleton::instance = nullptr;
2.饿汉式单例(Eager Initialization)
饿汉式单例在类加载时就创建实例,保证线程安全,但如果该实例占用资源较多,可能会导致不必要的资源浪费。
class Singleton {
private:
static Singleton* instance;
Singleton() {} // 私有构造函数
public:
static Singleton* getInstance() {
return instance;
}
// 禁止拷贝和赋值操作
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
// 初始化静态成员
Singleton* Singleton::instance = new Singleton();
3.线程安全的懒汉式单例
通过双重检查锁定(Double-Checked Locking)机制实现线程安全的懒汉式单例。
#include <mutex>
class Singleton {
private:
static Singleton* instance;
static std::mutex mtx;
Singleton() {} // 私有构造函数
public:
static Singleton* getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
// 禁止拷贝和赋值操作
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
// 初始化静态成员
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
4. 使用C++11的线程安全局部静态变量
C++11标准保证了局部静态变量的线程安全性,简化了单例模式的实现。
class Singleton {
private:
Singleton() {} // 私有构造函数
public:
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
// 禁止拷贝和赋值操作
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
工厂方法模式
工厂方法模式(Factory Method Pattern)是创建型设计模式之一,也被称作简单工厂模式,它通过定义一个创建对象的接口来处理对象的实例化,具体的实现由子类来完成。这样做的好处是使得客户端代码依赖于抽象接口而不是具体的实现类,从而提高了系统的灵活性和可扩展性。
工厂方法模式通常涉及以下几个角色:
- 产品(Product):定义了工厂方法所创建的对象的接口。
- 具体产品(ConcreteProduct):实现了产品接口的具体类。
- 工厂(Creator):声明了返回产品对象的工厂方法,通常是一个抽象类或接口。
- 具体工厂(ConcreteCreator):实现了工厂方法以创建具体产品的实例。
优缺点
优点
- 遵循开闭原则:可以在不修改现有代码的情况下引入新的产品类型。
- 单一职责原则:将产品创建代码与产品使用代码分离。
- 提高代码的灵活性和可扩展性:可以方便地替换或增加产品。
缺点
- 增加代码复杂度:需要为每一个具体产品创建一个具体工厂类。
- 引入大量子类:当产品种类较多时,会增加系统的复杂度和维护难度。
应用场景
- 当一个类不知道它所需要的对象的具体类时。工厂方法模式将对象的创建延迟到子类,从而实现了解耦。
- 当一个类希望由其子类来指定创建对象时。通过定义工厂方法,基类可以把创建对象的责任转移给子类。
- 当类的实例化过程涉及较多逻辑时。将这些逻辑集中在一个工厂方法中,可以简化客户端代码。
实现方法
1.产品接口和具体产品
// 抽象产品
class Product {
public:
virtual ~Product() {}
virtual std::string operation() const = 0;
};
// 具体产品A
class ConcreteProductA : public Product {
public:
std::string operation() const override {
return "Result of the ConcreteProductA";
}
};
// 具体产品B
class ConcreteProductB : public Product {
public:
std::string operation() const override {
return "Result of the ConcreteProductB";
}
};
2. 工厂接口和具体工厂
// 抽象工厂
class Creator {
public:
virtual ~Creator() {}
virtual Product* factoryMethod() const = 0;
std::string someOperation() const {
Product* product = this->factoryMethod();
std::string result = "Creator: The same creator's code has just worked with " + product->operation();
delete product;
return result;
}
};
// 具体工厂A
class ConcreteCreatorA : public Creator {
public:
Product* factoryMethod() const override {
return new ConcreteProductA();
}
};
// 具体工厂B
class ConcreteCreatorB : public Creator {
public:
Product* factoryMethod() const override {
return new ConcreteProductB();
}
};
3. 客户端代码
// 接受一个工厂对象作为参数
void ClientCode(const Creator& creator) {
std::cout << "Client: I'm not aware of the creator's class, but it still works.\n"
<< creator.someOperation() << std::endl;
}
int main() {
std::cout << "App: Launched with the ConcreteCreatorA.\n";
Creator* creator = new ConcreteCreatorA();
ClientCode(*creator);
delete creator;
std::cout << "App: Launched with the ConcreteCreatorB.\n";
creator = new ConcreteCreatorB();
ClientCode(*creator);
delete creator;
return 0;
}
策略模式
策略模式(Strategy Pattern)是一种行为设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。策略模式使得算法可以独立于使用它的客户端变化。它让算法的变化独立于使用算法的客户,使得代码更加灵活和可扩展。
策略模式涉及以下几个角色:
- 上下文(Context):也叫环境,维护一个策略对象的引用。
- 策略接口(Strategy):定义一个算法家族的接口。
- 具体策略(ConcreteStrategy):实现了算法接口的具体策略类。
优缺点
优点
- 遵循开闭原则:可以在不修改上下文类的情况下引入新的策略;
- 避免多重条件语句:通过使用策略模式,可以消除代码中的条件分支语句;
- 提高代码的灵活性和可维护性:将算法和业务逻辑分离,使得代码更加清晰。
缺点
- 增加对象数量:每个具体策略都是一个独立的类,这会增加类的数量;
- 策略切换开销:频繁切换策略可能会带来一定的开销。
应用场景
- 多个类只有在算法或行为上稍有不同的场景:通过策略模式可以定义一系列可重用的算法或行为;
- 需要在不同的时间点应用不同的算法的场景:策略模式可以将每个算法封装到策略类中,在运行时动态选择算法;
- 消除条件分支语句的场景:将条件分支语句中的不同算法或行为提取到策略类中。
实现方法
1.策略和具体策略
// 策略接口
class Strategy {
public:
virtual ~Strategy() {}
virtual void StrategyMethod() const = 0;
};
// 具体策略A
class ConcreteStrategyA : public Strategy {
public:
void StrategyMethod() const override {
std::cout << "ConcreteStrategyA: 执行算法A" << std::endl;
}
};
// 具体策略B
class ConcreteStrategyB : public Strategy {
public:
void StrategyMethod() const override {
std::cout << "ConcreteStrategyB: 执行算法B" << std::endl;
}
};
2.上下文类
// 上下文类
class Context {
private:
Strategy* strategy;
public:
Context(Strategy* strategy = nullptr) : strategy(strategy) {}
~Context() { delete strategy; }
void setStrategy(Strategy* strategy) {
delete this->strategy;
this->strategy = strategy;
}
void executeStrategy() const {
if (this->strategy) {
this->strategy->execute();
} else {
std::cout << "Context: 策略未设置" << std::endl;
}
}
};
3.客户端代码
int main() {
Context* context = new Context(new ConcreteStrategyA());
context->executeStrategy();
context->setStrategy(new ConcreteStrategyB());
context->executeStrategy();
delete context;
return 0;
}
观察者模式
观察者模式(Observer Pattern)是一种行为设计模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。观察者模式广泛应用于事件处理系统中,如GUI事件监听、数据模型与视图同步等。
由观察者模式的特性还可产生一些变体,例如推模型将主题更新主动推送给观察者,拉模型是指观察者主动从主题拉取更新信息,还有事件驱动模型,结合事件机制实现更加灵活的通知机制。
观察者模式包含角色如下:
- 主题(Subject):也称为被观察者,维护一个观察者列表,并在自身状态发生变化时通知所有观察者。
- 观察者(Observer):定义一个更新接口,用于接收主题的通知。
- 具体主题(ConcreteSubject):实现了主题接口,包含一些状态,当状态变化时,通知所有观察者。
- 具体观察者(ConcreteObserver):实现了观察者接口,更新自身状态以与主题保持一致。
优缺点
优点
- 解耦:观察者模式将观察者和主题解耦,使得它们可以独立变化。
- 灵活性:增加或减少观察者都很方便,不需要修改主题代码。
- 符合开闭原则:可以在不修改现有代码的情况下增加新的观察者。
缺点
- 潜在的性能问题:如果观察者过多,通知的时间会变长,影响性能。
- 复杂性增加:观察者和主题之间的依赖关系可能会比较复杂,维护起来困难。
- 可能出现循环依赖:不当的设计可能导致观察者和主题之间的循环调用,造成系统崩溃。
应用场景
- 事件处理系统:如GUI应用程序中的事件监听。
- 数据模型与视图同步:如MVC框架中的视图与模型同步。
- 订阅-发布系统:如消息队列系统、实时数据推送系统。
实现方法
1.主题及具体主题
#include <iostream>
#include <vector>
#include <algorithm>
// 观察者接口
class Observer {
public:
virtual ~Observer() {}
virtual void update(const std::string& message_from_subject) = 0;
};
// 主题接口
class Subject {
public:
virtual ~Subject() {}
virtual void attach(Observer* observer) = 0;
virtual void detach(Observer* observer) = 0;
virtual void notify() = 0;
};
// 具体主题
class ConcreteSubject : public Subject {
private:
std::vector<Observer*> observers;
std::string message;
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(message);
}
}
void createMessage(const std::string& message) {
this->message = message;
notify();
}
};
2.观察者及具体观察者
// 具体观察者
class ConcreteObserver : public Observer {
private:
std::string message_from_subject;
ConcreteSubject& subject;
public:
ConcreteObserver(ConcreteSubject& subject) : subject(subject) {
this->subject.attach(this);
}
~ConcreteObserver() {
subject.detach(this);
}
void update(const std::string& message_from_subject) override {
this->message_from_subject = message_from_subject;
printInfo();
}
void printInfo() {
std::cout << "ConcreteObserver: 收到通知,消息为:" << message_from_subject << std::endl;
}
};
3.客户端代码
int main() {
ConcreteSubject* subject = new ConcreteSubject();
ConcreteObserver* observer1 = new ConcreteObserver(*subject);
ConcreteObserver* observer2 = new ConcreteObserver(*subject);
ConcreteObserver* observer3 = new ConcreteObserver(*subject);
subject->createMessage("Hello World!");
subject->createMessage("Observer Pattern Example");
delete observer3;
delete observer2;
delete observer1;
delete subject;
return 0;
}
装饰器模式
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。装饰器模式提供了一种更灵活的方式来扩展对象的功能,符合开闭原则,并且有助于职责分离。
装饰器模式的结构如下
- 组件(Component):定义一个对象接口,可以动态地为该对象添加职责。
- 具体组件(ConcreteComponent):实现组件接口的具体对象,可以为其添加职责。
- 装饰器(Decorator):实现组件接口,内部包含一个组件对象,并在其基础上增加职责。
- 具体装饰器(ConcreteDecorator):实现装饰器类,并在其基础上增加额外的行为。
优缺点
优点
- 更灵活的扩展方式:装饰器模式提供了一种比继承更灵活的方式来扩展对象的功能,避免了继承导致的类爆炸问题。
- 符合开闭原则:可以在不修改现有代码的情况下,通过组合不同的装饰器来增加新功能。
- 职责分离:可以将不同的功能分离到不同的装饰器中,实现单一职责原则。
缺点
- 增加复杂性:装饰器模式会增加系统中类和对象的数量,可能导致代码的复杂性增加。
- 调试困难:由于功能是通过多个装饰器叠加实现的,可能会导致调试和排错变得更加困难。
应用场景
- 需要动态地添加或删除功能:装饰器模式适用于那些需要动态地添加或删除功能的系统,如图形界面组件的装饰。
- 替代继承:当无法使用继承来扩展类的功能时,可以使用装饰器模式。
- 职责划分:将不同的功能分离到不同的装饰器中,有助于职责划分和代码复用。
实现方法
1.组件及具体组件
#include <iostream>
#include <string>
// 组件接口
class Component {
public:
virtual ~Component() {}
virtual std::string operation() const = 0;
};
// 具体组件
class ConcreteComponent : public Component {
public:
std::string operation() const override {
return "ConcreteComponent";
}
};
2.装饰器和具体装饰器
// 装饰器类
class Decorator : public Component {
protected:
Component* component;
public:
Decorator(Component* component) : component(component) {}
std::string operation() const override {
return component->operation();
}
};
// 具体装饰器A
class ConcreteDecoratorA : public Decorator {
public:
ConcreteDecoratorA(Component* component) : Decorator(component) {}
std::string operation() const override {
return "ConcreteDecoratorA(" + Decorator::operation() + ")";
}
};
// 具体装饰器B
class ConcreteDecoratorB : public Decorator {
public:
ConcreteDecoratorB(Component* component) : Decorator(component) {}
std::string operation() const override {
return "ConcreteDecoratorB(" + Decorator::operation() + ")";
}
};
3.客户端代码
void ClientCode(const Component& component) {
std::cout << "RESULT: " << component.operation() << std::endl;
}
int main() {
Component* simple = new ConcreteComponent();
std::cout << "Client: I've got a simple component:\n";
ClientCode(*simple);
std::cout << "\n";
Component* decorator1 = new ConcreteDecoratorA(simple);
Component* decorator2 = new ConcreteDecoratorB(decorator1);
std::cout << "Client: Now I've got a decorated component:\n";
ClientCode(*decorator2);
delete simple;
delete decorator1;
delete decorator2;
return 0;
}