优秀的编程知识分享平台

网站首页 > 技术文章 正文

创建型设计模式:构建高效灵活的C++架构

nanyue 2024-12-19 15:29:15 技术文章 3 ℃

设计模式的几大原则

设计模式的原则是软件设计中用于创建更灵活、可维护和可扩展的系统的基本指导原则。这些原则帮助开发者在设计过程中做出更好的决策,避免常见的设计错误。以下是一些重要的设计模式原则:

1. 单一职责原则 (Single Responsibility Principle, SRP)

  • 定义:一个类应该只有一个引起它变化的原因,即一个类只负责一项职责。
  • 目的:减少类的复杂性,使其更易于理解和维护。如果一个类承担多种职责,那么每种职责的变化都可能影响到这个类。

2. 开放封闭原则 (Open/Closed Principle, OCP)

  • 定义:软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。
  • 目的:当需求变化时,可以通过扩展原有功能而不是修改已有代码来实现新的需求,从而提高系统的稳定性和灵活性。

3. 里氏替换原则 (Liskov Substitution Principle, LSP)

  • 定义:子类型必须能够替换掉它们的基类型,且不影响程序的正确性。
  • 目的:确保继承关系的正确性,即子类对象能完全替代父类对象,使得父类的调用者能够透明地使用子类的实例。

4. 依赖倒置原则 (Dependency Inversion Principle, DIP)

  • 定义:高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
  • 目的:通过依赖抽象(接口或抽象类),而不是具体实现,来减少类之间的耦合性,提高系统的灵活性和可维护性。

5. 接口隔离原则 (Interface Segregation Principle, ISP)

  • 定义:客户端不应该被迫依赖于它不使用的方法,应该将庞大的接口拆分为更小、更具体的接口,使得客户端只需了解它所需的接口。
  • 目的:通过接口分离降低系统的复杂性,避免由于接口臃肿导致的实现类的复杂性。

6. 合成复用原则 (Composite Reuse Principle, CRP)

  • 定义:优先使用对象组合(即“has-a”关系)而不是继承(即"is-a"关系)来达到代码复用的目的。
  • 目的:通过组合来提高代码的复用性和灵活性,减少继承带来的紧耦合问题。

7. 迪米特法则 (Law of Demeter, LoD)

  • 定义:一个对象应该对其他对象有尽可能少的了解,即“最少知识原则”。
  • 目的:减少对象之间的依赖关系,降低系统的耦合度,从而提高系统的模块化和维护性。

8. 优先使用组合而不是继承

  • 定义:与继承相比,优先使用对象组合来复用代码。
  • 目的:通过组合,类之间的依赖性较低,系统结构更灵活,容易扩展。

总结

这些设计原则在实践中相互作用,共同促进了高质量的代码设计。理解并运用这些原则,可以帮助开发者设计出更易维护、更具弹性的软件系统。

设计模式的分类

在软件设计中,创建型、结构型和行为型是设计模式的三种主要分类。这三类模式根据其用途和作用范围的不同,解决软件开发中的不同问题。

创建型模式

定义:创建型模式关注对象的创建过程,主要解决对象创建时的复杂性问题。

作用:

  • 为创建对象提供更灵活、更高效、更符合扩展需求的方法。
  • 通过抽象化的方式控制对象创建的细节和时机。

常见创建型模式:

  • 工厂方法模式(Factory Method):定义一个用于创建对象的接口,让子类决定实例化哪一个类。
  • 抽象工厂模式(Abstract Factory):提供一个创建相关或依赖对象的接口,而无需指定具体类。
  • 单例模式(Singleton):确保一个类只有一个实例,并提供一个全局访问点。
  • 建造者模式(Builder):将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。
  • 原型模式(Prototype):通过复制现有对象来创建新对象,而不是通过实例化类。

结构型模式

定义:结构型模式关注对象的组织方式,主要解决如何更好地组织类和对象,使其更灵活、更高效。

作用:

  • 处理对象的组合、继承和接口适配问题。
  • 简化系统的结构,增强系统的可维护性和扩展性。

常见结构型模式:

  • 适配器模式(Adapter):将一个类的接口转换成客户期望的另一个接口。
  • 桥接模式(Bridge):将抽象部分与实现部分分离,使它们可以独立变化。
  • 组合模式(Composite):将对象组合成树形结构以表示“整体-部分”层次结构,使客户可以一致地处理单个对象和组合对象。
  • 装饰器模式(Decorator):动态地为对象添加新的职责,而不改变其结构。
  • 外观模式(Facade):为子系统提供一个统一的接口,简化子系统的使用。
  • 享元模式(Flyweight):通过共享尽量多的相似对象,减少内存使用。
  • 代理模式(Proxy):为另一个对象提供一个替身或占位符,以控制对该对象的访问。

行为型模式

定义:行为型模式关注的是对象之间的职责分配和通信方式,主要解决对象交互和职责分配的问题。

作用:

  • 处理对象之间的动态交互关系。
  • 增强对象之间的松耦合,简化复杂的控制流。
  • 关注如何通过对象之间的协作完成任务。

常见行为型模式:

  • 策略模式(Strategy):定义一组算法,将每个算法封装到独立的类中,并且可以相互替换。
  • 观察者模式(Observer):定义对象间一对多的依赖关系,当一个对象状态变化时,所有依赖它的对象都会收到通知。
  • 命令模式(Command):将请求封装为对象,以便支持参数化的命令、队列、日志等功能。
  • 责任链模式(Chain of Responsibility):将请求沿着处理者链传递,直到被某个处理者处理。
  • 状态模式(State):允许对象在内部状态改变时改变其行为。
  • 模板方法模式(Template Method):定义算法的框架,将一些步骤推迟到子类中实现。
  • 中介者模式(Mediator):通过中介者对象封装对象之间的交互,使其解耦。
  • 迭代器模式(Iterator):提供一种访问集合对象元素的方法,而不暴露其内部表示。
  • 备忘录模式(Memento):保存对象的某个状态,以便在以后可以恢复。
  • 访问者模式(Visitor):定义一个新操作,可以作用于一组对象,而不改变这些对象的类。

三者的对比如下:

分类

关注点

目标

模式示例

行为型

对象之间的职责分配与动态交互

简化对象间通信、增强灵活性与松耦合

策略、观察者、命令、状态、责任链、模板方法等

结构型

对象与类的组合方式

简化系统结构、优化对象之间的依赖关系

适配器、桥接、组合、装饰器、外观、代理等

创建型

对象的创建方式

提高对象创建的灵活性、控制复杂对象的创建过程

工厂方法、抽象工厂、单例、建造者、原型等

创建型模式

单例模式

单例模式(Singleton Pattern)是一种创建型设计模式,确保一个类只有一个实例,并提供全局访问点。

使用 C++11 引入的 std::once_flag 和 std::call_once,可以实现线程安全的单例模式。std::once_flag 是 C++ 标准库中的同步工具,用于确保某段代码只执行一次,通常用于初始化。

#include <iostream>
#include <mutex>
#include <memory> // std::unique_ptr

class Singleton {
private:
    static std::unique_ptr<Singleton> instance; // 静态实例指针
    static std::once_flag initFlag;            // 用于线程安全初始化的标志

    // 私有构造函数,防止直接实例化
    Singleton() {
        std::cout << "Singleton constructor called!" << std::endl;
    }

public:
    // 禁用拷贝构造和赋值运算符
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 获取单例实例的方法
    static Singleton* getInstance() {
        // 保证初始化代码只执行一次
        std::call_once(initFlag, []() {
            instance.reset(new Singleton());
        });
        return instance.get();
    }

    // 业务方法
    void doSomething() {
        std::cout << "Singleton instance doing something!" << std::endl;
    }
};

// 初始化静态成员
std::unique_ptr<Singleton> Singleton::instance = nullptr;
std::once_flag Singleton::initFlag;

int main() {
    // 获取单例实例
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();

    // 调用方法
    s1->doSomething();

    // 验证实例是否相同
    if (s1 == s2) {
        std::cout << "s1 and s2 are the same instance!" << std::endl;
    }

    return 0;
}

工厂方法

工厂方法模式(Factory Method Pattern)定义了一个创建对象的接口,但由子类决定实例化哪一个类。工厂方法将对象的实例化推迟到子类。

我们以 UI 控件(按钮 Button) 为例:

  • 系统需要支持多种平台(如 Windows 和 Mac)。
  • 不同平台的按钮具有不同的外观和行为。
  • 客户端代码应该与按钮的具体实现无关,只需调用按钮的通用接口。

工厂方法模式的核心思路

  • 将创建按钮的逻辑抽象为一个工厂方法,让子类工厂决定创建哪种具体按钮。
  • 客户端只需通过工厂接口创建按钮,无需了解具体按钮的实现细节。

工厂方法模式代码实现

// 1. 定义按钮接口(抽象产品类)
#include <iostream>
#include <memory>

// 按钮接口
class Button {
public:
    virtual ~Button() = default;
    virtual void render() = 0;  // 渲染按钮
    virtual void onClick() = 0; // 按钮点击事件
};
// 2. 创建具体按钮类(具体产品类)
// Windows 风格按钮
class WindowsButton : public Button {
public:
    void render() override {
        std::cout << "Rendering Windows Button" << std::endl;
    }
    void onClick() override {
        std::cout << "Windows Button Clicked!" << std::endl;
    }
};

// Mac 风格按钮
class MacButton : public Button {
public:
    void render() override {
        std::cout << "Rendering Mac Button" << std::endl;
    }
    void onClick() override {
        std::cout << "Mac Button Clicked!" << std::endl;
    }
};

// 3. 创建按钮工厂接口(抽象工厂类)
// 按钮工厂接口
class ButtonFactory {
public:
    virtual ~ButtonFactory() = default;
    virtual std::unique_ptr<Button> createButton() = 0; // 工厂方法:创建按钮
};

// 4. 创建具体工厂类(具体工厂类)
// Windows 按钮工厂
class WindowsButtonFactory : public ButtonFactory {
public:
    std::unique_ptr<Button> createButton() override {
        return std::make_unique<WindowsButton>();
    }
};

// Mac 按钮工厂
class MacButtonFactory : public ButtonFactory {
public:
    std::unique_ptr<Button> createButton() override {
        return std::make_unique<MacButton>();
    }
};

int main() {
    // 创建 Windows 风格按钮的工厂
    std::unique_ptr<ButtonFactory> factory = std::make_unique<WindowsButtonFactory>();
    auto button = factory->createButton(); // 使用工厂方法创建按钮
    button->render();
    button->onClick();

    // 创建 Mac 风格按钮的工厂
    factory = std::make_unique<MacButtonFactory>();
    button = factory->createButton(); // 使用工厂方法创建按钮
    button->render();
    button->onClick();

    return 0;
}

工厂方法模式的特点和分析

  • 封装对象创建:客户端代码只与工厂接口和产品接口交互,不关心具体产品类的实现。
  • 扩展性好:新增产品(如 Linux 按钮)时,只需添加对应的具体产品类和具体工厂类,无需修改现有代码。
  • 符合开闭原则:扩展具体产品和具体工厂时,不需要修改抽象工厂和抽象产品的定义。

抽象工厂方法

抽象工厂模式(Abstract Factory Pattern)是一种创建型设计模式,提供一个接口,用于创建一组相关或相互依赖的对象,而无需指定具体类。

核心思想

  • 抽象工厂:定义创建对象的接口。
  • 具体工厂:实现接口,负责生成具体产品。
  • 产品接口:定义具体产品的通用方法。
  • 具体产品:实现产品接口,表示具体的产品实例。
  • 客户端:通过抽象工厂使用产品,而不依赖具体工厂或产品的实现。

抽象工厂模式的优势是将产品族的创建与使用分离,方便扩展。

使用场景

  • 需要生成一组相关或相互依赖的对象(例如不同风格的按钮和文本框)。
  • 需要避免依赖具体类,隐藏对象创建的实现细节。
  • 需要支持产品族的扩展。

以下是使用抽象工厂模式的 C++ 实现示例。假设我们要创建两种风格的用户界面组件:Windows 风格和 Mac 风格。

#include <iostream>
#include <memory>

// 产品接口:按钮
class Button {
public:
    virtual ~Button() = default;
    virtual void render() = 0; // 渲染按钮
};

// 产品接口:文本框
class TextBox {
public:
    virtual ~TextBox() = default;
    virtual void render() = 0; // 渲染文本框
};

// 具体产品:Windows 按钮
class WindowsButton : public Button {
public:
    void render() override {
        std::cout << "Rendering Windows Button" << std::endl;
    }
};

// 具体产品:Windows 文本框
class WindowsTextBox : public TextBox {
public:
    void render() override {
        std::cout << "Rendering Windows TextBox" << std::endl;
    }
};

// 具体产品:Mac 按钮
class MacButton : public Button {
public:
    void render() override {
        std::cout << "Rendering Mac Button" << std::endl;
    }
};

// 具体产品:Mac 文本框
class MacTextBox : public TextBox {
public:
    void render() override {
        std::cout << "Rendering Mac TextBox" << std::endl;
    }
};

// 抽象工厂:UI 工厂
class UIFactory {
public:
    virtual ~UIFactory() = default;
    virtual std::unique_ptr<Button> createButton() = 0;   // 创建按钮
    virtual std::unique_ptr<TextBox> createTextBox() = 0; // 创建文本框
};

// 具体工厂:Windows UI 工厂
class WindowsUIFactory : public UIFactory {
public:
    std::unique_ptr<Button> createButton() override {
        return std::make_unique<WindowsButton>();
    }
    std::unique_ptr<TextBox> createTextBox() override {
        return std::make_unique<WindowsTextBox>();
    }
};

// 具体工厂:Mac UI 工厂
class MacUIFactory : public UIFactory {
public:
    std::unique_ptr<Button> createButton() override {
        return std::make_unique<MacButton>();
    }
    std::unique_ptr<TextBox> createTextBox() override {
        return std::make_unique<MacTextBox>();
    }
};

// 客户端
void renderUI(UIFactory& factory) {
    auto button = factory.createButton();   // 创建按钮
    auto textBox = factory.createTextBox(); // 创建文本框

    button->render();
    textBox->render();
}

int main() {
    WindowsUIFactory windowsFactory;
    MacUIFactory macFactory;

    std::cout << "Rendering Windows UI:" << std::endl;
    renderUI(windowsFactory); // 使用 Windows 工厂

    std::cout << "\nRendering Mac UI:" << std::endl;
    renderUI(macFactory); // 使用 Mac 工厂

    return 0;
}

代码解析

  • 产品接口: Button 和 TextBox 是两个独立的产品接口,分别定义了按钮和文本框的通用行为。
  • 具体产品: WindowsButton 和 MacButton 分别是 Windows 和 Mac 风格的按钮实现。 WindowsTextBox 和 MacTextBox 分别是 Windows 和 Mac 风格的文本框实现。
  • 抽象工厂: UIFactory 定义了创建按钮和文本框的方法。
  • 具体工厂: WindowsUIFactory 和 MacUIFactory 实现了具体的产品创建逻辑。
  • 客户端: renderUI 函数通过抽象工厂接口创建按钮和文本框,而不依赖于具体工厂或具体产品。

原型模式

原型模式允许通过复制现有对象来创建新对象,而不是直接实例化类。这种模式提供了一种简化对象创建的方式,尤其是在对象的创建过程非常复杂时。

组成部分:

  • 原型接口(Prototype):定义一个 clone 方法,用于克隆自身。
  • 具体原型(Concrete Prototype):实现 clone 方法,定义如何复制自身。
  • 客户端(Client):使用原型接口创建新对象。

适用场景:

  • 创建对象的成本较高,直接创建对象不够高效(例如需要大量计算或访问数据库)。
  • 系统需要大量相似对象,且对象的状态可通过复制得到。
  • 想隐藏具体类的实现细节,通过抽象接口操作对象。

C++实现示例:

假设我们有一个复杂的图形类需要频繁创建,例如一个带有位置和颜色的形状对象。

#include <iostream>
#include <string>
#include <memory>

// 原型接口
class Shape {
public:
    virtual ~Shape() = default;
    virtual Shape* clone() const = 0;  // 克隆方法
    virtual void draw() const = 0;     // 展示形状信息
};

// 具体原型:圆形
class Circle : public Shape {
private:
    int x_, y_, radius_;
    std::string color_;

public:
    Circle(int x, int y, int radius, const std::string& color)
        : x_(x), y_(y), radius_(radius), color_(color) {}

    // 实现克隆方法
    Circle* clone() const override {
        return new Circle(*this); // 使用拷贝构造函数实现深拷贝
    }

    void draw() const override {
        std::cout << "Circle: Position(" << x_ << ", " << y_ 
                  << "), Radius: " << radius_ 
                  << ", Color: " << color_ << std::endl;
    }
};

// 具体原型:矩形
class Rectangle : public Shape {
private:
    int x_, y_, width_, height_;
    std::string color_;

public:
    Rectangle(int x, int y, int width, int height, const std::string& color)
        : x_(x), y_(y), width_(width), height_(height), color_(color) {}

    // 实现克隆方法
    Rectangle* clone() const override {
        return new Rectangle(*this); // 使用拷贝构造函数实现深拷贝
    }

    void draw() const override {
        std::cout << "Rectangle: Position(" << x_ << ", " << y_ 
                  << "), Width: " << width_ 
                  << ", Height: " << height_ 
                  << ", Color: " << color_ << std::endl;
    }
};

// 客户端
int main() {
    // 创建一个圆形原型
    Shape* circlePrototype = new Circle(10, 20, 15, "Red");
    Shape* rectanglePrototype = new Rectangle(5, 5, 30, 40, "Blue");

    // 克隆圆形对象
    Shape* clonedCircle = circlePrototype->clone();
    Shape* clonedRectangle = rectanglePrototype->clone();

    // 显示克隆对象信息
    clonedCircle->draw();
    clonedRectangle->draw();

    // 清理资源
    delete circlePrototype;
    delete rectanglePrototype;
    delete clonedCircle;
    delete clonedRectangle;

    return 0;
}

建造者模式

什么是建造者模式?

建造者模式(Builder Pattern)是一种创建型设计模式,它允许我们一步一步地构建复杂对象。与直接用构造函数创建对象不同,建造者模式将对象的创建过程与其表示分离,使得同样的构建过程可以创建不同的表示。

解决的问题

在对象创建过程中,某些对象的构造过程可能会变得复杂且繁琐,比如需要设置多个属性或依赖于其他对象。构造函数的参数列表可能会变得过长,不仅难以维护,还容易出现错误。

建造者模式解决了以下问题

  • 简化对象创建过程:将复杂对象的创建步骤分解并封装在不同的方法中,使对象的创建过程更加清晰。
  • 解耦对象的创建和表示:将对象的创建过程与它们的最终表示形式分离开来,使得同样的构建过程可以创建不同的对象。
  • 提高代码可读性和维护性:通过将对象的构建步骤分离出来,使代码更具可读性和维护性。

建造者模式的组成

建造者模式通常包含以下几部分:

  • 产品(Product):最终构建出来的复杂对象。通常是一个包含多个部分的对象。
  • 抽象建造者(Builder):定义创建产品各个部分的抽象方法。
  • 具体建造者(ConcreteBuilder):实现抽象建造者接口,负责具体构建产品的各个部分,并提供一个用于获取最终产品的接口。
  • 指挥者(Director):负责管理构建过程。它使用建造者来构建产品,通常会按照特定的顺序构建产品的各个部分。
  • 客户端(Client):使用建造者来构建产品,但不需要关心产品的具体构建细节。

C++ 代码示例:

假设我们需要构建一个复杂的对象,表示一个“汽车”对象,包含发动机、车轮和车身等部件。我们使用建造者模式来解耦汽车的构建过程。

#include <iostream>
#include <string>

// 产品类:汽车
class Car {
public:
    void setEngine(const std::string& engine) {
        engine_ = engine;
    }

    void setWheels(int wheels) {
        wheels_ = wheels;
    }

    void setBody(const std::string& body) {
        body_ = body;
    }

    void show() {
        std::cout << "Car details: " << std::endl;
        std::cout << "Engine: " << engine_ << std::endl;
        std::cout << "Wheels: " << wheels_ << std::endl;
        std::cout << "Body: " << body_ << std::endl;
    }

private:
    std::string engine_;
    int wheels_;
    std::string body_;
};

// 抽象建造者:建造者类
class CarBuilder {
public:
    virtual void buildEngine() = 0;
    virtual void buildWheels() = 0;
    virtual void buildBody() = 0;
    virtual Car* getResult() = 0;
    virtual ~CarBuilder() = default;
};

// 具体建造者:构建具体汽车
class SportsCarBuilder : public CarBuilder {
private:
    Car* car;

public:
    SportsCarBuilder() {
        car = new Car();
    }

    void buildEngine() override {
        car->setEngine("V8 Engine");
    }

    void buildWheels() override {
        car->setWheels(4);  // 运动型车需要四个轮子
    }

    void buildBody() override {
        car->setBody("Sports Body");
    }

    Car* getResult() override {
        return car;
    }

    ~SportsCarBuilder() {
        delete car;
    }
};

// 具体建造者:构建SUV
class SUVCarBuilder : public CarBuilder {
private:
    Car* car;

public:
    SUVCarBuilder() {
        car = new Car();
    }

    void buildEngine() override {
        car->setEngine("V6 Engine");
    }

    void buildWheels() override {
        car->setWheels(4);  // SUV车也需要四个轮子
    }

    void buildBody() override {
        car->setBody("SUV Body");
    }

    Car* getResult() override {
        return car;
    }

    ~SUVCarBuilder() {
        delete car;
    }
};

// 指挥者:指导建造过程
class CarDirector {
private:
    CarBuilder* builder;

public:
    CarDirector(CarBuilder* builder) : builder(builder) {}

    // 指挥者负责建造整个汽车
    void construct() {
        builder->buildEngine();
        builder->buildWheels();
        builder->buildBody();
    }

    Car* getCar() {
        return builder->getResult();
    }
};

// 客户端
int main() {
    // 选择建造者
    CarBuilder* builder = new SportsCarBuilder();
    CarDirector director(builder);

    director.construct();
    Car* car = director.getCar();
    car->show();

    // 清理资源
    delete car;
    delete builder;

    // 构建另一种类型的车
    builder = new SUVCarBuilder();
    director = CarDirector(builder);

    director.construct();
    car = director.getCar();
    car->show();

    delete car;
    delete builder;

    return 0;
}

代码解析

  • Car 是产品类,它包含汽车的多个部分(如发动机、车轮和车身)。
  • CarBuilder 是抽象建造者,定义了构建汽车各个部分的接口。
  • SportsCarBuilder 和 SUVCarBuilder 是具体建造者,负责实现如何构建不同类型的汽车。
  • CarDirector 是指挥者,负责协调构建过程并决定构建的顺序。
  • Client 使用建造者模式来创建具体的汽车。

总结

建造者模式提供了一种灵活的方式来创建复杂对象。它将对象的创建过程分解成多个步骤,并允许这些步骤以不同的顺序或方式组合,从而使得同样的构造过程可以生成不同的对象。通过建造者模式,代码的可读性和可维护性得到了提升,并且减少了创建复杂对象时可能出现的错误。

最近发表
标签列表