三种模式
模板方法
- 定义
模板方法模式是一种行为型设计模式,它定义了一个算法的骨架,并允许子类在不改变算法结构的情况下,重新定义算法的某些步骤。也就是说,模板方法模式将一个操作的步骤分离为抽象和具体方法,父类提供了算法的框架,而具体的步骤由子类实现。 - 背景
在软件设计中,某些操作的流程是固定的,但个别步骤可能会有所不同。使用模板方法模式可以将这些固定的部分提取到基类中,而将变化的部分留给子类去实现。这是一个典型的"模板-钩子"模式,它使得算法的核心结构可复用,而每个步骤则可以由不同的子类定制。 - 要点
- 算法的固定部分:模板方法模式中的算法的结构是固定的。
- 步骤的灵活部分:某些步骤可以通过抽象方法的形式交由子类来实现。
- 父类定义算法骨架:模板方法模式的核心思想是将固定部分写在父类中,变化的部分交给子类去实现。
- 本质
模板方法模式的本质在于封装算法结构,通过父类定义整体的框架和步骤,子类只需要实现具体的操作部分,保证了算法结构的统一性和复用性。 - 结构图
cpp
AbstractClass
| (Template Method)
|-------------------|
ConcreteClass1 ConcreteClass2
| |
(Implementation) (Implementation)
AbstractClass
:定义了模板方法,包含一系列算法步骤的调用顺序。部分步骤为抽象方法,需要由具体子类实现。ConcreteClass1 / ConcreteClass2
:继承自AbstractClass
,实现了抽象方法,完成具体的步骤。
示例:制作饮料(Tea 和 Coffee)
- 假设我们有一个制作饮料的流程,不同饮料(如茶和咖啡)具有不同的制作方法。虽然制作过程的框架相似,但每种饮料的制作步骤(如泡茶或煮咖啡)是不同的。
cpp
#include <iostream>
#include <string>
using namespace std;
// 抽象类:定义制作饮料的模板方法
class Drink {
public:
// 模板方法:定义饮料制作的算法框架
void prepare() {
boilWater(); // 煮水
brew(); // 冲泡
pourInCup(); // 倒入杯中
addCondiments(); // 添加配料
}
// 这些方法是模板中的具体步骤
virtual void brew() = 0; // 冲泡:这是一个抽象方法,子类需要实现
virtual void addCondiments() = 0; // 添加配料:子类需要实现
// 一些默认的实现
void boilWater() {
cout << "Boiling water..." << endl;
}
void pourInCup() {
cout << "Pouring into cup..." << endl;
}
};
// 具体类:制作茶
class Tea : public Drink {
public:
void brew() override {
cout << "Steeping the tea..." << endl; // 茶叶浸泡
}
void addCondiments() override {
cout << "Adding lemon..." << endl; // 加入柠檬
}
};
// 具体类:制作咖啡
class Coffee : public Drink {
public:
void brew() override {
cout << "Dripping coffee through filter..." << endl; // 用滤纸过滤咖啡
}
void addCondiments() override {
cout << "Adding sugar and milk..." << endl; // 加入糖和牛奶
}
};
// 使用模板方法
int main() {
Tea tea;
Coffee coffee;
cout << "Making tea:" << endl;
tea.prepare(); // 使用模板方法制作茶
cout << "\nMaking coffee:" << endl;
coffee.prepare(); // 使用模板方法制作咖啡
return 0;
}
代码解释
抽象类 Drink
:
prepare()
方法是模板方法,定义了饮料制作的步骤。它按照固定的顺序调用了boilWater()、brew()、pourInCup() 和 addCondiments()
。brew()
和addCondiments()
是纯虚函数(即抽象方法),子类必须实现这些方法以提供具体的冲泡和配料步骤。boilWater()
和pourInCup()
是已实现的默认方法,所有饮料的制作过程都可以共用。
具体类 Tea
和 Coffee
:
-
Tea
类实现了brew()
方法,通过"Steeping the tea"
来模拟茶叶的冲泡过程,并实现了addCondiments()
,加入柠檬作为配料。 -
Coffee
类实现了brew()
方法,通过"Dripping coffee through filter"
来模拟咖啡的冲泡过程,并实现了addCondiments()
,加入糖和牛奶。
主程序:
- 在主函数中,我们分别创建了
Tea
和Coffee
对象,并调用了prepare()
方法。通过模板方法
prepare()
,制作饮料的过程被标准化,而具体的冲泡和添加配料的步骤由子类提供。
输出结果:
csharp
Making tea:
Boiling water...
Steeping the tea...
Pouring into cup...
Adding lemon...
Making coffee:
Boiling water...
Dripping coffee through filter...
Pouring into cup...
Adding sugar and milk...
- 模板方法模式的优点和缺点
优点:
-
代码复用:模板方法模式将相同的步骤提取到抽象类中,避免了子类中重复实现这些通用步骤。
-
控制算法的执行顺序:模板方法定义了整个算法的执行顺序,子类无法改变这个结构,只能修改具体的步骤。
-
可扩展性:可以方便地通过继承扩展不同的具体实现,而不需要修改已有的代码。
缺点:
- 过度依赖继承:模板方法模式依赖继承,可能导致类的层次过深,增加了子类与父类之间的耦合性。
- 灵活性受限:虽然子类可以覆盖某些方法,但整个算法的结构(步骤顺序)是固定的,无法灵活调整。
- 代码复用受限:在某些情况下,如果不同的子类之间的变化比较多,使用模板方法模式可能会导致代码的重复实现。
适用场景:
模板方法模式特别适用于那些操作步骤相同、但某些具体步骤不同的场景。例如:
- 游戏的处理流程、文档生成流程、数据处理流程等。
- 不同的产品制造过程,它们的步骤顺序相同,只有某些具体步骤不同。
总结:
模板方法模式通过抽象化算法的结构并将具体的步骤交给子类来实现,使得可以在保持算法结构不变的前提下定制化具体实现。它适用于需要在固定流程中灵活插入不同行为的情况。
观察者模式
- 定义
观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,使得当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。也称为"发布-订阅"模式。 - 背景
在软件开发中,往往有一些对象的状态发生变化时,需要通知到多个其他对象。通过使用观察者模式,可以使得目标对象(被观察者)与观察者对象之间解耦,减少了对象间的直接依赖,使得系统更具可扩展性和灵活性。 - 要点
- 一对多关系:一个对象(主题)状态变化时,通知所有依赖于它的对象(观察者)。
- 松耦合:主题与观察者之间没有直接耦合关系。观察者通过接口接收主题的更新通知,而不需要直接操作主题。
- 动态更新:当主题的状态发生变化时,所有观察者都会立即收到通知并更新。
- 本质
观察者模式的本质是解耦,它使得观察者与主题之间没有直接的依赖关系。观察者对象通过注册机制接收主题对象的变化通知,并根据主题的状态变化做出相应的响应。 - 结构图
cpp
Subject (被观察者)
|
|--- registerObserver(Observer)
|--- removeObserver(Observer)
|--- notifyObservers()
Observer (观察者)
|--- update()
ConcreteSubject (具体主题)
|--- notifyObservers()
ConcreteObserver (具体观察者)
|--- update()
Subject
:定义观察者的注册、删除和通知接口。管理所有的观察者。Observer
:观察者接口,定义update()
方法,所有的具体观察者都需要实现此方法。ConcreteSubject
:实现了Subject
接口,具体管理观察者并在状态变化时通知它们。ConcreteObserver
:实现了Observer
接口,在收到主题的通知时更新自身的状态。
C++ 示例:天气预报
假设有一个天气预报系统,当天气变化时,所有的显示设备(如天气显示板、手机应用等)都会被通知更新数据。
cpp
#include <iostream>
#include <vector>
#include <string>
// 观察者接口,所有具体观察者类需要实现这个接口
class Observer {
public:
// 更新方法,当主题的状态发生变化时,观察者会被通知,并更新状态
virtual void update(float temperature, float humidity, float pressure) = 0;
virtual ~Observer() = default; // 虚析构函数,确保派生类对象能够正确销毁
};
// 主题接口,所有具体主题类需要实现这个接口
class Subject {
public:
// 注册观察者
virtual void registerObserver(Observer* observer) = 0;
// 移除观察者
virtual void removeObserver(Observer* observer) = 0;
// 通知所有注册的观察者
virtual void notifyObservers() = 0;
virtual ~Subject() = default;
};
// 具体主题:气象数据,保存当前气象数据并通知观察者
class WeatherData : public Subject {
private:
float temperature; // 当前温度
float humidity; // 当前湿度
float pressure; // 当前气压
std::vector<Observer*> observers; // 观察者列表
public:
// 注册观察者
void registerObserver(Observer* observer) override {
observers.push_back(observer);
}
// 移除观察者
void removeObserver(Observer* observer) override {
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
}
// 通知所有观察者
void notifyObservers() override {
for (Observer* observer : observers) {
observer->update(temperature, humidity, pressure);
}
}
// 测量数据变化时,通知观察者
void measurementsChanged() {
notifyObservers(); // 数据变化后通知所有观察者
}
// 设置新的气象数据并触发通知
void setMeasurements(float temp, float hum, float pres) {
temperature = temp;
humidity = hum;
pressure = pres;
measurementsChanged(); // 更新数据后通知观察者
}
};
// 具体观察者:天气显示板
class WeatherDisplay : public Observer {
private:
std::string name; // 显示板的名称
public:
// 构造函数,设置显示板的名称
WeatherDisplay(const std::string& n) : name(n) {}
// 更新方法,接收主题的最新气象数据并显示
void update(float temperature, float humidity, float pressure) override {
std::cout << name << " - Temperature: " << temperature << "C, "
<< "Humidity: " << humidity << "%, "
<< "Pressure: " << pressure << "hPa" << std::endl;
}
};
// 主程序,模拟天气数据变化并通知显示板更新
int main() {
WeatherData weatherData; // 创建一个气象数据对象
WeatherDisplay display1("Display1"); // 创建显示板1
WeatherDisplay display2("Display2"); // 创建显示板2
// 注册两个显示板作为观察者
weatherData.registerObserver(&display1);
weatherData.registerObserver(&display2);
// 设置新数据并通知所有观察者
weatherData.setMeasurements(25.3, 65, 1013);
weatherData.setMeasurements(27.0, 60, 1010);
// 移除显示板2,之后只有显示板1会接收到通知
weatherData.removeObserver(&display2);
weatherData.setMeasurements(22.0, 70, 1015);
return 0;
}
Observer
类:定义了一个观察者接口,所有具体的观察者类需要实现 update()方法,这样当主题的状态变化时,观察者可以接收到通知并更新自身的状态。Subject
类:定义了一个主题接口,所有具体的主题类需要实现注册、移除和通知观察者的功能。WeatherData
类就是一个具体的主题实现。ConcreteObserver
:WeatherDisplay
类实现了Observer
接口,每当接收到新的气象数据时,显示板会显示最新的状态。WeatherData
类:它保存气象数据,并在数据变化时通知所有注册的观察者。通过setMeasurements()
方法模拟气象数据的变化。
策略模式
- 定义
策略模式是一种行为型设计模式,它定义了一系列算法,并将每个算法封装起来,使得它们可以互换。策略模式使得算法的变化独立于使用算法的客户端。 - 背景
在许多情况下,系统中有多个算法或操作可以替代使用,而选择哪种算法往往在运行时决定。策略模式使得不同的算法封装成独立的策略对象,客户端可以根据需要选择合适的策略。这有助于避免复杂的条件判断语句,并提高代码的扩展性和可维护性。 - 要点
- 封装算法:策略模式通过将算法封装为独立的策略类,避免了算法代码散布在多个地方。
- 算法独立:算法可以独立于使用它的客户端而变化。算法之间互不影响,客户端可以动态地切换策略。
- 避免条件语句:通过封装不同的策略类,避免了使用大量的条件语句来选择不同的算法。
- 本质
策略模式的本质在于封装变动的行为,将变化的行为封装到不同的策略类中,通过上下文类动态选择不同的策略执行。策略模式将策略(算法)和使用它们的代码分离,提高了代码的灵活性和可扩展性。 - 结构图
cpp
Context
|
|--- setStrategy(Strategy)
|--- executeStrategy()
Strategy (策略接口)
|--- algorithmInterface()
ConcreteStrategyA (具体策略A)
|--- algorithmInterface()
ConcreteStrategyB (具体策略B)
|--- algorithmInterface()
Context
:上下文类,持有一个Strategy
类型的策略对象,负责设置和调用策略。Strategy
:策略接口,定义了所有具体策略类需要实现的算法接口。ConcreteStrategyA / ConcreteStrategyB
:具体的策略类,提供不同的算法实现。
代码示例:支付策略
cpp
#include <iostream>
#include <memory>
// 策略接口,定义支付行为的接口
class PaymentStrategy {
public:
// 支付方法,所有具体策略类都必须实现此方法
virtual void pay(double amount) = 0;
virtual ~PaymentStrategy() = default;
};
// 具体策略:支付宝支付
class Alipay : public PaymentStrategy {
public:
void pay(double amount) override {
std::cout << "Paying " << amount << " using Alipay." << std::endl;
}
};
// 具体策略:微信支付
class WeChatPay : public PaymentStrategy {
public:
void pay(double amount) override {
std::cout << "Paying " << amount << " using WeChatPay." << std::endl;
}
};
// 具体策略:信用卡支付
class CreditCardPay : public PaymentStrategy {
public:
void pay(double amount) override {
std::cout << "Paying " << amount << " using CreditCard." << std::endl;
}
};
// 上下文类,使用策略类来执行支付
class PaymentContext {
private:
std::unique_ptr<PaymentStrategy> strategy; // 当前的支付策略
public:
// 构造函数,初始化策略
PaymentContext(std::unique_ptr<PaymentStrategy> paymentStrategy)
: strategy(std::move(paymentStrategy)) {}
// 设置新的支付策略
void setStrategy(std::unique_ptr<PaymentStrategy> newStrategy) {
strategy = std::move(newStrategy);
}
// 执行支付
void executePayment(double amount) {
strategy->pay(amount); // 根据当前策略执行支付
}
};
// 主程序,模拟选择不同支付方式支付订单
int main() {
PaymentContext context(std::make_unique<Alipay>()); // 使用支付宝支付
context.executePayment(100.0);
context.setStrategy(std::make_unique<WeChatPay>()); // 更换为微信支付
context.executePayment(200.0);
context.setStrategy(std::make_unique<CreditCardPay>()); // 更换为信用卡支付
context.executePayment(300.0);
return 0;
}
PaymentStrategy
类:这是一个支付策略的抽象类,定义了支付行为的接口
pay()
,不同的支付方式(如支付宝、微信支付、信用卡支付)通过继承该类来实现不同的支付行为。ConcreteStrategy
类:Alipay、WeChatPay、CreditCardPay
等类是具体的支付策略类,它们实现了pay()
方法,表示具体的支付方式。PaymentContext
类:它是上下文类,负责与客户端交互。它包含一个PaymentStrategy
类型的成员变量,通过setStrategy()
方法动态改变支付策略,然后通过executePayment()
方法执行支付。- 主程序:主程序模拟了通过不同支付方式支付订单。通过调用
setStrategy()
方法切换支付策略,可以动态地选择支付方式。