一)C++ 的 23 种设计模式概述
1)创建型模式(Creational Patterns)
1,单例模式(Singleton):确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。例如,在一个系统中,数据库连接池通常可以设计为单例模式,以避免创建多个连接池对象浪费资源。
2,工厂方法模式(Factory Method):定义一个创建对象的接口,但让子类决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。比如,在游戏开发中,不同类型的游戏角色(战士、法师等)的创建可以使用工厂方法模式,由具体的工厂子类负责创建不同类型的角色。
3,抽象工厂模式(Abstract Factory):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。当需要创建一组相关的产品对象时,如在图形界面库中,创建不同主题(如明亮主题、暗黑主题)下的按钮、文本框等一系列组件时可以使用抽象工厂模式。
4,建造者模式(Builder):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。例如,在创建一个复杂的电脑配置对象时,使用建造者模式可以分别设置 CPU、内存、硬盘等部件,最后得到完整的电脑配置对象。
5,原型模式(Prototype):用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。在一些图形编辑软件中,当需要快速创建多个相似的图形对象(如多个相同形状和颜色的圆形)时,可以使用原型模式,通过复制已有图形对象来创建新的对象。
2)结构型模式(Structural Patterns)
1,代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问。例如,在网络访问中,当访问一些外部资源时,可以使用代理服务器来代替客户端进行访问,代理服务器可以进行权限验证、缓存数据等操作。
2,装饰器模式(Decorator):动态地给一个对象添加一些额外的职责。就像在咖啡订单系统中,咖啡是基础对象,而可以通过添加奶泡、焦糖等装饰器来动态地改变咖啡的口味和价格,这些装饰器都继承自咖啡的抽象类并且可以嵌套使用。
3,适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口。比如,当有一个旧的接口不符合新的系统要求时,可以使用适配器模式来进行转换。例如,在电源适配器中,将国外的电源接口标准转换为国内的电源接口标准,使得电器能够正常使用。
4,桥接模式(Bridge):将抽象部分与它的实现部分分离,使它们都可以独立地变化。例如,在图形绘制系统中,形状(如圆形、矩形)是抽象部分,而颜色(如红色、蓝色)是实现部分,通过桥接模式可以将形状和颜色分离,使得形状可以使用不同的颜色进行绘制,并且形状和颜色的变化相互独立。
5,组合模式(Composite):将对象组合成树形结构以表示 “部分 - 整体” 的关系。例如,在文件系统中,文件夹可以包含文件和子文件夹,这就是一种组合关系,使用组合模式可以方便地对文件系统进行遍历、添加、删除等操作。
6,外观模式(Facade):为子系统中的一组接口提供一个一致的界面,这个界面使得这组接口更容易使用。例如,在一个复杂的家庭影院系统中,包括电视、音响、蓝光播放器等多个设备,外观模式可以提供一个简单的遥控器界面来统一控制这些设备,用户不需要分别操作每个设备。
3)行为型模式(Behavioral Patterns)
1,模板方法模式(Template Method):在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。例如,在制作饮品的过程中,烧水和倒入杯子的步骤是固定的,而冲泡和添加调料的步骤因饮品(如咖啡、茶)而异,模板方法模式可以定义制作饮品的通用流程,让子类实现具体的冲泡和添加调料步骤。
2,策略模式(Strategy):定义了一系列的算法,并将每个算法封装起来,而且使它们可以相互替换。在游戏中的角色移动方式可以使用策略模式,如行走、奔跑、跳跃等移动策略可以封装为不同的类,角色可以根据不同的情况(如在不同的地形上)选择不同的移动策略。
3,命令模式(Command):将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。例如,在图形编辑软件中,用户的每个操作(如绘制图形、移动图形等)都可以看作是一个命令,这些命令可以被记录、撤销和重做。
4,职责链模式(Chain of Responsibility):为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。在工作流系统中,一个任务审批流程可以使用职责链模式,例如,一个文件审批可能需要经过多个部门(如部门主管、经理、总经理)的审批,每个部门都可以看作是职责链中的一个节点。
5,状态模式(State):允许一个对象在其内部状态改变时改变它的行为。比如,在电梯控制系统中,电梯有上升、下降、停止等状态,当电梯处于不同状态时,它的行为(如开门、关门、运行等)是不同的,通过状态模式可以方便地管理电梯的状态和行为。
6,观察者模式(Observer):定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象的状态发生变化时,会通知所有观察者对象。在股票市场系统中,股票价格是主题对象,多个投资者(观察者)关注股票价格的变化,当股票价格变化时,会通知所有投资者。
7,中介者模式(Mediator):用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地变化。在一个聊天系统中,聊天服务器可以看作是中介者,多个用户(客户端)通过聊天服务器进行通信,用户之间不需要直接建立连接。
8,迭代器模式(Iterator):提供一种方法顺序访问一个聚合对象中的各个元素,而不需要暴露该聚合对象的内部表示。在容器类(如数组、链表、集合等)中,迭代器模式可以用于遍历容器中的元素,使得遍历操作与容器的具体实现分离。
9,访问者模式(Visitor):表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。例如,在一个公司的组织结构中,有员工和部门等元素,访问者模式可以用于实现对这些元素的不同操作,如统计员工人数、计算部门预算等。
二)模板方法模式(Template Method Pattern)定义
基本定义:模板方法模式是一种行为设计模式,它在一个抽象基类中定义了一个算法的骨架(即模板方法),这个模板方法由一系列方法调用组成。其中一些方法是抽象方法(纯虚函数),这些抽象方法代表了算法中的可变步骤,它们的具体实现被延迟到子类中;还有一些方法可以是钩子函数(非纯虚函数,有默认实现),子类可以选择重写这些钩子函数来改变算法的行为。
目的和作用:
1)代码复用:通过在抽象基类中定义算法的主要流程,多个子类可以复用这个流程。例如,在一个数据处理管道的模板方法中,数据读取、数据存储等通用步骤可以在基类中定义一次,不同的数据处理子类(如数据加密子类、数据清洗子类)可以复用这些步骤,只需要关注数据处理的核心可变步骤。
2)可扩展性和灵活性:允许子类通过实现抽象方法或重写钩子函数来定制算法的特定部分。这样,当需要增加新的算法变体时,只需要创建一个新的子类并实现相应的抽象方法或修改钩子函数即可。例如,在一个游戏角色升级系统的模板方法中,升级流程的基本框架(如检查是否达到升级条件、更新角色等级等)是固定的,但是不同角色(战士、法师等)的经验获取方式和属性提升方式可以通过子类实现抽象方法来定制,方便扩展新的角色类型。
3)模板方法的基本结构
1,抽象基类(Abstract Base Class):
包含模板方法和抽象方法(纯虚函数)或者钩子函数。模板方法定义了算法的骨架,它按照一定的顺序调用其他方法。抽象方法是需要在子类中具体实现的方法,它们代表了算法中可变的部分。钩子函数是一种可选的方法,子类可以选择重写它们来改变算法的默认行为。
2,具体子类(Concrete Sub - classes):
继承自抽象基类,并且实现抽象方法。通过实现这些抽象方法,子类为模板方法中的可变部分提供了具体的实现,从而定制了整个算法的行为。
三)举例
1)代码实现示例(以文件读取处理为例)
1,抽象基类定义算法骨架和抽象步骤:

#include <iostream>
#include <fstream>
#include <string>
class FileReader 
{
public:
    // 模板方法:定义文件读取处理的流程
    void readAndProcessFile(const std::string& filePath) 
    {
        std::ifstream file(filePath);
        if (file.is_open()) 
        {
            readHeader(file);
            std::string line;
            while (std::getline(file, line)) 
            {
                processLine(line);
            }
            file.close();
            postProcess();
        } else 
        {
            std::cerr << "无法打开文件: " << filePath << std::endl;
        }
    }
    // 抽象方法:读取文件头,由子类实现具体的读取逻辑
    virtual void readHeader(std::ifstream& file) = 0;
    // 抽象方法:处理每行内容,由子类实现具体的处理方式
    virtual void processLine(const std::string& line) = 0;
    // 钩子函数:文件读取后的后处理,默认不做任何事,子类可重写
    virtual void postProcess() {}
};

具体子类实现抽象步骤和定制行为:

// 具体子类:CSV文件读取处理器
class CSVFileReader : public FileReader
 {
public:
    void readHeader(std::ifstream& file) override
     {
        std::string header;
        std::getline(file, header);
        std::cout << "CSV文件头: " << header << std::endl;
    }
    void processLine(const std::string& line) override
     {
        std::cout << "处理CSV行: " << line << std::endl;
    }
};
// 具体子类:JSON文件读取处理器
class JSONFileReader : public FileReader 
{
public:
    void readHeader(std::ifstream& file) override 
    {
        // JSON文件可能没有传统意义的头,这里简单输出提示
        std::cout << "开始读取JSON文件内容" << std::endl;
    }
    void processLine(const std::string& line) override
     {
        std::cout << "处理JSON数据: " << line << std::endl;
    }
    void postProcess() override 
    {
        std::cout << "完成JSON文件读取后的额外处理" << std::endl;
    }
};

使用示例:

int main() 
{
    std::string csvFilePath = "example.csv";
    std::string jsonFilePath = "example.json";
    CSVFileReader csvReader;
    csvReader.readAndProcessFile(csvFilePath);
    JSONFileReader jsonReader;
    jsonReader.readAndProcessFile(jsonFilePath);
    return 0;
}

代码解释:
在FileReader抽象基类中,readAndProcessFile是模板方法,它定义了文件读取和处理的基本流程:打开文件、读取文件头、逐行处理文件内容、关闭文件以及可选的后处理。readHeader和processLine是抽象方法,postProcess是钩子函数。
CSVFileReader和JSONFileReader是具体子类,它们分别实现了readHeader和processLine方法来处理不同格式文件的读取和处理,JSONFileReader还重写了postProcess钩子函数来进行额外的处理。这种方式使得文件读取处理的主要流程在抽象基类中固定,而具体的细节根据文件格式在子类中实现,提高了代码的复用性和可扩展性。
钩子函数则是在抽象基类中有默认实现的非纯虚函数,子类可以选择性地重写它们来改变模板方法的行为。
2)template method 在GUI设计中的应用
窗口创建与初始化,抽象基类定义窗口基本流程:
在 GUI 设计中,可以创建一个抽象基类Window来定义窗口创建和初始化的模板方法。这个模板方法包含了设置窗口属性、创建窗口部件等通用步骤。

#include <iostream>
// 抽象基类代表窗口
class Window 
{
public:
    // 模板方法:创建和初始化窗口
    void createAndInitialize() 
    {
        setWindowProperties();
        createWidgets();
        arrangeWidgets();
    }
    // 抽象方法:设置窗口属性,如大小、标题等,由子类具体实现
    virtual void setWindowProperties() = 0;
    // 抽象方法:创建窗口部件,如按钮、文本框等,由子类具体实现
    virtual void createWidgets() = 0;
    // 抽象方法:排列窗口部件,由子类具体实现
    virtual void arrangeWidgets() = 0;
};

具体子类实现特定窗口类型:
例如,创建一个LoginWindow子类用于登录窗口的实现。

class LoginWindow : public Window 
{
public:
    void setWindowProperties() override 
    {
        std::cout << "设置登录窗口属性:大小为300x200,标题为'登录'" << std::endl;
    }
    void createWidgets() override
    {
        std::cout << "创建登录窗口部件:用户名文本框、密码文本框、登录按钮" << std::endl;
    }
    void arrangeWidgets() override
     {
        std::cout << "排列登录窗口部件:文本框在上,按钮在下" << std::endl;
    }
};

事件处理机制
抽象基类定义事件处理流程:
可以在抽象基类中定义一个模板方法来处理事件,如鼠标点击、键盘按键等。这个模板方法包含了事件接收、事件分发和事件响应等步骤。
// 抽象基类扩展,加入事件处理

class WindowWithEvents : public Window 
{
public:
    // 模板方法:处理事件
    void handleEvents() 
    {
        receiveEvent();
        dispatchEvent();
        respondToEvent();
    }
    // 抽象方法:接收事件,如从消息队列获取事件,由子类具体实现
    virtual void receiveEvent() = 0;
    // 抽象方法:分发事件,如根据事件类型分发给不同的处理函数,由子类具体实现
    virtual void dispatchEvent() = 0;
    // 抽象方法:响应事件,如更新窗口状态或执行特定操作,由子类具体实现
    virtual void respondToEvent() = 0;
};

具体子类实现特定事件处理:
以ButtonClickEvent为例,在LoginWindow子类中实现事件处理。

class LoginWindowWithEvents : public WindowWithEvents 
{
public:
    void receiveEvent() override 
    {
        std::cout << "接收登录按钮点击事件" << std::endl;
    }
    void dispatchEvent() override
     {
        std::cout << "将登录按钮点击事件分发给登录处理函数" << std::endl;
    }
    void respondToEvent() override 
    {
        std::cout << "响应登录按钮点击事件:验证用户名和密码" << std::endl;
    }
};

界面更新与重绘
抽象基类定义界面更新流程:
创建一个模板方法用于界面更新和重绘,包括检查更新需求、更新数据和重绘界面等步骤。
// 抽象基类扩展,加入界面更新

class WindowWithUpdate : public Window 
{
public:
    // 模板方法:更新和重绘界面
    void updateAndRedraw() 
    {
        checkUpdateNeeded();
        updateData();
        redrawInterface();
    }
    // 抽象方法:检查是否需要更新,如根据数据变化或用户操作判断,由子类具体实现
    virtual void checkUpdateNeeded() = 0;
    // 抽象方法:更新数据,如从数据库获取新数据或修改内部状态,由子类具体实现
    virtual void updateData() = 0;
    // 抽象方法:重绘界面,根据新数据重新绘制窗口部件,由子类具体实现
    virtual void redrawInterface() = 0;
};

具体子类实现特定更新操作:
对于一个DataDisplayWindow子类(用于显示数据的窗口),可以这样实现更新操作。

class DataDisplayWindow : public WindowWithUpdate 
{
public:
    void checkUpdateNeeded() override 
    {
        std::cout << "检查数据显示窗口是否需要更新:根据数据变化标志判断" << std::endl;
    }
    void updateData() override 
    {
        std::cout << "更新数据显示窗口的数据:从数据源获取新数据" << std::endl;
    }
    void redrawInterface() override 
    {
        std::cout << "重绘数据显示窗口界面:更新数据显示部件" << std::endl;
    }
};

3)网络通信模板
在网络通信模板方法中,需要考虑各种可能出现的错误情况,如连接失败、数据传输错误等。

class NetworkCommunication 
{
public:
    // 模板方法,进行网络通信
    bool communicate() 
    {
        if (establishConnection()) 
        {
            if (sendData()) 
            {
                if (receiveData()) 
                {
                    closeConnection();
                    return true;
                } else 
                {
                    handleReceiveError();
                }
            } else
            {
                handleSendError();
            }
        } else
         {
            handleConnectionError();
        }
        return false;
    }
    // 抽象方法,建立连接,由子类实现
    virtual bool establishConnection() = 0;
    // 抽象方法,发送数据,由子类实现
    virtual bool sendData() = 0;
    // 抽象方法,接收数据,由子类实现
    virtual bool receiveData() = 0;
    // 钩子函数,处理连接错误,默认打印错误信息,子类可重写
    virtual void handleConnectionError() 
    {
        std::cerr << "Connection error." << std::endl;
    }
    // 钩子函数,处理发送数据错误,默认打印错误信息,子类可重写
    virtual void handleSendError() 
    {
        std::cerr << "Send data error." << std::endl;
    }
    // 钩子函数,处理接收数据错误,默认打印错误信息,子类可重写
    virtual void handleReceiveError() 
    {
        std::cerr << "Receive data error." << std::endl;
    }
    // 钩子函数,关闭连接,默认正常关闭,子类可重写
    virtual void closeConnection() 
    {
        std::cout << "Connection closed." << std::endl;
    }
};

在这个例子中,communicate是模板方法,它按照建立连接、发送数据、接收数据、关闭连接的顺序执行网络通信操作。如果在任何一个步骤出现错误,会调用相应的钩子函数来处理错误。子类可以根据具体的网络协议和应用场景重写这些钩子函数,提供更有效的错误处理方式。
4)图形绘制系统
假设要构建一个图形绘制系统,支持绘制多种图形(如圆形、矩形等)。首先定义一个抽象基类,其中的模板方法包含绘制图形的基本步骤。

class Graphics 
{
public:
    // 模板方法,定义绘制图形的通用流程
    void draw() 
    {
        setupDrawingEnvironment();
        drawShape();
        cleanupDrawingEnvironment();
    }
    // 抽象方法,用于绘制具体形状,由子类实现
    virtual void drawShape() = 0;
protected:
    void setupDrawingEnvironment()
     {
        std::cout << "Setting up the drawing environment." << std::endl;
    }
    void cleanupDrawingEnvironment()
     {
        std::cout << "Cleaning up the drawing environment." << std::endl;
    }
};

在这个例子中,draw是模板方法,它定义了绘制图形的通用步骤:设置绘图环境、绘制具体形状、清理绘图环境。drawShape是抽象方法,留给子类实现具体的图形绘制步骤。这种设计将绘制过程中相对固定的部分(设置和清理环境)在基类中实现,而将可变的部分(绘制具体形状)通过抽象方法让子类来完成,使得算法骨架清晰明了。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐