C++ 策略模式与桥接模式:解耦算法与实现,让代码像变形金刚一样灵活
嘿,各位程序员朋友们!有没有遇到过这种情况:代码写着写着,突然发现一个类承担了太多的责任,就像一个超负荷的搬运工,身兼数职,累得喘不过气?更糟糕的是,稍稍改动一下其中一个功能,就可能牵一发而动全身,整个系统都跟着颤抖?
别担心,这都是软件设计中的常见问题。今天我们就来聊聊两个解耦利器,C++的策略模式和桥接模式,它们就像变形金刚一样,能让你的代码灵活多变,轻松应对各种需求变更。
策略模式:让算法像乐高积木一样随意组合
想象一下,你要设计一个电商网站的支付系统。刚开始,可能只有支付宝支付,简单粗暴地写在支付类里。后来,业务发展迅速,接入了微信支付、银联支付、信用卡支付等等。如果都写在同一个类里,这个类就会变得臃肿不堪,代码也变得难以维护和扩展。
这时,策略模式就派上用场了!策略模式的核心思想是将算法封装成独立的策略类,然后让客户端可以根据需要选择不同的策略。就像乐高积木一样,你可以随意组合不同的积木(策略),搭建出不同的模型(支付方式)。
策略模式的结构很简单:
- Context(上下文): 维护一个对 Strategy 对象的引用,用于执行具体的算法。就像电商网站的支付页面,它需要知道当前选择的是哪种支付方式。
- Strategy(策略接口): 定义所有支持的算法的公共接口。就像支付接口,定义了
pay()
方法,所有具体的支付方式都需要实现这个方法。 - ConcreteStrategy(具体策略): 实现 Strategy 接口,封装具体的算法。就像支付宝支付、微信支付等,它们各自实现了
pay()
方法,执行不同的支付逻辑。
用代码说话:
#include <iostream>
#include <string>
// 策略接口
class PaymentStrategy {
public:
virtual void pay(int amount) = 0;
virtual ~PaymentStrategy() {} // 确保多态的正确析构
};
// 具体策略:支付宝支付
class AlipayPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "使用支付宝支付:" << amount << "元" << std::endl;
// 模拟支付宝支付逻辑
}
};
// 具体策略:微信支付
class WechatPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "使用微信支付:" << amount << "元" << std::endl;
// 模拟微信支付逻辑
}
};
// 上下文:支付上下文
class PaymentContext {
private:
PaymentStrategy* strategy;
public:
PaymentContext(PaymentStrategy* strategy) : strategy(strategy) {}
void setStrategy(PaymentStrategy* strategy) {
this->strategy = strategy;
}
void payAmount(int amount) {
strategy->pay(amount);
}
~PaymentContext() {
delete strategy; // 释放策略对象的内存
}
};
int main() {
// 创建支付宝支付策略
PaymentStrategy* alipay = new AlipayPayment();
// 创建支付上下文,并设置支付策略为支付宝
PaymentContext context(alipay);
// 使用支付宝支付
context.payAmount(100);
// 切换到微信支付
context.setStrategy(new WechatPayment()); // 注意,这里需要先创建新的策略对象
// 使用微信支付
context.payAmount(200);
// 记得释放内存
delete context.strategy; // 因为在`setStrategy`时,context指向了新的策略,原来的策略没有被删除
// alipay已经由PaymentContext析构函数删除,无需再次删除
return 0;
}
策略模式的优点:
- 算法可以自由切换: 客户端可以根据需要动态选择不同的算法,无需修改原有代码。
- 可扩展性好: 增加新的算法非常容易,只需要实现 Strategy 接口即可。
- 避免了大量的条件判断: 将不同的算法封装成独立的类,避免了在一个类中出现大量的
if-else
或switch
语句。 - 符合开闭原则: 对扩展开放,对修改关闭。
什么时候使用策略模式?
- 当一个类需要支持多种算法,并且客户端需要能够动态选择算法时。
- 当算法的实现细节需要对客户端隐藏时。
- 当需要避免大量的条件判断语句时。
桥接模式:让抽象与实现像桥梁一样分离
现在我们再来考虑另一个场景:你要设计一个图形库,支持不同的图形(圆形、矩形、三角形)和不同的渲染引擎(OpenGL、DirectX)。如果采用传统的继承方式,每增加一种图形或渲染引擎,都需要创建新的子类,导致类的数量呈指数级增长,形成可怕的“类爆炸”。
这时,桥接模式就能优雅地解决这个问题。桥接模式的核心思想是将抽象部分与实现部分分离,使它们可以独立变化。就像一座桥梁,连接了河两岸,让车辆可以自由通行。
桥接模式的结构:
- Abstraction(抽象类): 定义抽象类的接口,维护一个对 Implementor 对象的引用。就像图形的抽象类,定义了
draw()
方法,但具体的绘制逻辑交给 Implementor 对象来完成。 - RefinedAbstraction(细化抽象类): 继承 Abstraction 类,扩展抽象类的功能。就像不同类型的图形(圆形、矩形、三角形),它们都继承了图形的抽象类,并实现了各自的绘制逻辑。
- Implementor(实现类接口): 定义实现类的接口,规定了实现类必须实现的方法。就像渲染引擎的接口,定义了
drawCircle()
,drawRectangle()
,drawTriangle()
等方法。 - ConcreteImplementor(具体实现类): 实现 Implementor 接口,提供具体的实现。就像 OpenGL 渲染引擎、DirectX 渲染引擎,它们各自实现了渲染引擎的接口,提供了不同的渲染逻辑。
用代码说话:
#include <iostream>
// 实现类接口:渲染引擎
class RenderingEngine {
public:
virtual void drawCircle(float radius) = 0;
virtual void drawRectangle(float width, float height) = 0;
virtual ~RenderingEngine() {}
};
// 具体实现类:OpenGL 渲染引擎
class OpenGLRenderingEngine : public RenderingEngine {
public:
void drawCircle(float radius) override {
std::cout << "OpenGL: 绘制圆形,半径:" << radius << std::endl;
// 模拟 OpenGL 绘制圆形的逻辑
}
void drawRectangle(float width, float height) override {
std::cout << "OpenGL: 绘制矩形,宽度:" << width << ", 高度:" << height << std::endl;
// 模拟 OpenGL 绘制矩形的逻辑
}
};
// 具体实现类:DirectX 渲染引擎
class DirectXRenderingEngine : public RenderingEngine {
public:
void drawCircle(float radius) override {
std::cout << "DirectX: 绘制圆形,半径:" << radius << std::endl;
// 模拟 DirectX 绘制圆形的逻辑
}
void drawRectangle(float width, float height) override {
std::cout << "DirectX: 绘制矩形,宽度:" << width << ", 高度:" << height << std::endl;
// 模拟 DirectX 绘制矩形的逻辑
}
};
// 抽象类:图形
class Shape {
protected:
RenderingEngine* renderingEngine;
public:
Shape(RenderingEngine* renderingEngine) : renderingEngine(renderingEngine) {}
virtual void draw() = 0;
virtual ~Shape() {}
};
// 细化抽象类:圆形
class Circle : public Shape {
private:
float radius;
public:
Circle(RenderingEngine* renderingEngine, float radius) : Shape(renderingEngine), radius(radius) {}
void draw() override {
renderingEngine->drawCircle(radius);
}
};
// 细化抽象类:矩形
class Rectangle : public Shape {
private:
float width;
float height;
public:
Rectangle(RenderingEngine* renderingEngine, float width, float height) : Shape(renderingEngine), width(width), height(height) {}
void draw() override {
renderingEngine->drawRectangle(width, height);
}
};
int main() {
// 创建 OpenGL 渲染引擎
RenderingEngine* openGL = new OpenGLRenderingEngine();
// 创建 DirectX 渲染引擎
RenderingEngine* directX = new DirectXRenderingEngine();
// 创建圆形,并使用 OpenGL 渲染引擎
Shape* circle = new Circle(openGL, 5.0f);
circle->draw();
// 创建矩形,并使用 DirectX 渲染引擎
Shape* rectangle = new Rectangle(directX, 10.0f, 20.0f);
rectangle->draw();
// 释放内存
delete circle;
delete rectangle;
delete openGL;
delete directX;
return 0;
}
桥接模式的优点:
- 抽象与实现分离: 抽象部分和实现部分可以独立变化,互不影响。
- 可扩展性好: 增加新的图形或渲染引擎非常容易,只需要创建新的子类或实现类即可。
- 避免了类的爆炸: 减少了类的数量,避免了复杂的继承层次结构。
什么时候使用桥接模式?
- 当一个类存在两个或多个独立变化的维度时。
- 当需要避免类的爆炸时。
- 当抽象部分和实现部分需要独立变化时。
策略模式 vs 桥接模式:傻傻分不清楚?
很多初学者容易混淆策略模式和桥接模式,它们都涉及到接口和实现的分离,但它们的应用场景和目的却有所不同。
- 策略模式: 主要用于封装不同的算法,让客户端可以动态选择算法。强调的是算法的可替换性。
- 桥接模式: 主要用于将抽象部分和实现部分分离,使它们可以独立变化。强调的是解耦和灵活性。
你可以这样理解:策略模式关注的是算法的选择,而桥接模式关注的是结构的分解。
总结:
策略模式和桥接模式都是非常强大的设计模式,它们可以帮助你编写出更加灵活、可维护和可扩展的代码。就像变形金刚一样,你的代码可以根据需要变形,轻松应对各种需求变更。
希望这篇文章能帮助你更好地理解和应用策略模式和桥接模式。记住,设计模式不是银弹,不要过度设计,要根据实际情况选择合适的模式。
现在,拿起你的键盘,开始实践吧!让你的代码也变得像变形金刚一样灵活!加油!