C++ 策略模式与桥接模式:解耦算法与实现,提升代码弹性

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-elseswitch 语句。
  • 符合开闭原则: 对扩展开放,对修改关闭。

什么时候使用策略模式?

  • 当一个类需要支持多种算法,并且客户端需要能够动态选择算法时。
  • 当算法的实现细节需要对客户端隐藏时。
  • 当需要避免大量的条件判断语句时。

桥接模式:让抽象与实现像桥梁一样分离

现在我们再来考虑另一个场景:你要设计一个图形库,支持不同的图形(圆形、矩形、三角形)和不同的渲染引擎(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 桥接模式:傻傻分不清楚?

很多初学者容易混淆策略模式和桥接模式,它们都涉及到接口和实现的分离,但它们的应用场景和目的却有所不同。

  • 策略模式: 主要用于封装不同的算法,让客户端可以动态选择算法。强调的是算法的可替换性
  • 桥接模式: 主要用于将抽象部分和实现部分分离,使它们可以独立变化。强调的是解耦和灵活性

你可以这样理解:策略模式关注的是算法的选择,而桥接模式关注的是结构的分解

总结:

策略模式和桥接模式都是非常强大的设计模式,它们可以帮助你编写出更加灵活、可维护和可扩展的代码。就像变形金刚一样,你的代码可以根据需要变形,轻松应对各种需求变更。

希望这篇文章能帮助你更好地理解和应用策略模式和桥接模式。记住,设计模式不是银弹,不要过度设计,要根据实际情况选择合适的模式。

现在,拿起你的键盘,开始实践吧!让你的代码也变得像变形金刚一样灵活!加油!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注