设计模式:模板方法模式:封装不变,扩展可变

news2024/9/17 7:10:59

目录

一、模板方法模式的定义与结构

二、模板方法模式的优点

三、模板方法模式的示例

示例一:

示例二:

四、总结


在软件开发中,设计模式是解决常见问题的可复用方案。其中,模板方法模式是一种行为型设计模式,它在不改变算法结构的情况下,允许子类重新定义算法中的某些步骤。

一、模板方法模式的定义与结构

模板方法模式由两部分组成:抽象类和具体子类。

抽象类:定义了模板方法和一系列抽象方法。模板方法封装了算法的骨架,包含了按照特定顺序调用的抽象方法和其他已经实现的方法。

具体子类:继承自抽象类,实现抽象类中的抽象方法,从而完成特定的算法步骤。

二、模板方法模式的优点

  1. 代码复用:将算法的通用部分封装在模板方法中,减少了重复代码的编写。
  2. 灵活性:允许子类在不改变算法结构的前提下,定制特定的步骤。
  3. 可维护性:清晰的结构使得代码更易于理解和维护。

三、模板方法模式的示例

示例一:

下面是一个简单的示例,假设我们要实现不同类型的文件读取操作。

#include <iostream>

// 抽象类:文件读取器
class FileReader {
public:
    // 模板方法
    void ReadFile() {
        OpenFile();
        ReadContents();
        CloseFile();
    }

    // 抽象方法:由子类实现
    virtual void OpenFile() = 0;
    virtual void ReadContents() = 0;
    virtual void CloseFile() = 0;
};

// 具体子类:文本文件读取器
class TextFileReader : public FileReader {
public:
    void OpenFile() override {
        std::cout << "Opening text file\n";
    }

    void ReadContents() override {
        std::cout << "Reading contents of text file\n";
    }

    void CloseFile() override {
        std::cout << "Closing text file\n";
    }
};

// 具体子类:二进制文件读取器
class BinaryFileReader : public FileReader {
public:
    void OpenFile() override {
        std::cout << "Opening binary file\n";
    }

    void ReadContents() override {
        std::cout << "Reading contents of binary file\n";
    }

    void CloseFile() override {
        std::cout << "Closing binary file\n";
    }
};

int main() {
    FileReader* textReader = new TextFileReader();
    FileReader* binaryReader = new BinaryFileReader();

    textReader->ReadFile();
    binaryReader->ReadFile();

    delete textReader;
    delete binaryReader;

    return 0;
}

在上述示例中,FileReader 是抽象类,定义了模板方法 ReadFile 和抽象方法 OpenFileReadContentsCloseFileTextFileReader 和 BinaryFileReader 是具体子类,分别实现了这些抽象方法。

示例二:

封装socket:

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#define Convert(addrptr) ((struct sockaddr *)addrptr)

namespace Net_Work
{
    const static int defaultsockfd = -1;
    const int backlog = 5;

    enum
    {
        SocketError = 1,
        BindError,
        ListenError,
    };

    // 封装一个基类,Socket接口类
    // 设计模式:模版方法模式
    /*
    这种设计方式将特定步骤的具体实现与操作流程分离开来,
    实现了代码的复用和扩展,从而提高代码质量和可维护性
    */
    class Socket
    {
    public:
        virtual ~Socket() {}
        virtual void CreateSocketOrDie() = 0;
        virtual void BindSocketOrDie(uint16_t port) = 0;
        virtual void ListenSocketOrDie(int backlog) = 0;
        virtual Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) = 0;
        virtual bool ConnectServer(std::string &serverip, uint16_t serverport) = 0;
        virtual int GetSockFd() = 0;
        virtual void SetSockFd(int sockfd) = 0;
        virtual void CloseSocket() = 0;
        virtual bool Recv(std::string *buffer, int size) = 0;
        virtual void Send(std::string &send_str) = 0;
        // TODO
    public:
        void BuildListenSocketMethod(uint16_t port, int backlog)
        {
            CreateSocketOrDie();
            BindSocketOrDie(port);
            ListenSocketOrDie(backlog);
        }
        bool BuildConnectSocketMethod(std::string &serverip, uint16_t serverport)
        {
            CreateSocketOrDie();
            return ConnectServer(serverip, serverport);
        }
        void BuildNormalSocketMethod(int sockfd)
        {
            SetSockFd(sockfd);
        }
    };

    class TcpSocket : public Socket
    {
    public:
        TcpSocket(int sockfd = defaultsockfd) : _sockfd(sockfd)
        {
        }
        ~TcpSocket()
        {
        }
        void CreateSocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
                exit(SocketError);
        }
        void BindSocketOrDie(uint16_t port) override
        {
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_addr.s_addr = INADDR_ANY;
            local.sin_port = htons(port);

            int n = ::bind(_sockfd, Convert(&local), sizeof(local));
            if (n < 0)
                exit(BindError);
        }
        void ListenSocketOrDie(int backlog) override
        {
            int n = ::listen(_sockfd, backlog);
            if (n < 0)
                exit(ListenError);
        }
        Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) override
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int newsockfd = ::accept(_sockfd, Convert(&peer), &len);
            if (newsockfd < 0)
                return nullptr;
            *peerport = ntohs(peer.sin_port);
            *peerip = inet_ntoa(peer.sin_addr);
            Socket *s = new TcpSocket(newsockfd);
            return s;
        }
        bool ConnectServer(std::string &serverip, uint16_t serverport) override
        {
            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(serverip.c_str());
            server.sin_port = htons(serverport);

            int n = ::connect(_sockfd, Convert(&server), sizeof(server));
            if (n == 0)
                return true;
            else
                return false;
        }
        int GetSockFd() override
        {
            return _sockfd;
        }
        void SetSockFd(int sockfd) override
        {
            _sockfd = sockfd;
        }
        void CloseSocket() override
        {
            if (_sockfd > defaultsockfd)
                ::close(_sockfd);
        }
        bool Recv(std::string *buffer, int size) override
        {
            char inbuffer[size];
            ssize_t n = recv(_sockfd, inbuffer, size-1, 0);
            if(n > 0)
            {
                inbuffer[n] = 0;
                *buffer += inbuffer; 
                return true;
            }
            else if(n == 0) return false;
            else return false;
        }
        void Send(std::string &send_str) override
        {
            
            send(_sockfd, send_str.c_str(), send_str.size(), 0);
        }
    private:
        int _sockfd;
    };

}

在这段代码中,Socket 类和 TcpSocket 类很好地体现了模板方法模式。

Socket 类是一个抽象基类,它定义了一系列纯虚函数,如 CreateSocketOrDieBindSocketOrDieListenSocketOrDie 等。这些函数代表了套接字操作的基本步骤,但具体的实现细节并未给出。

同时,Socket 类中还定义了一些非纯虚函数,如 BuildListenSocketMethodBuildConnectSocketMethod 和 BuildNormalSocketMethod,这些函数构成了模板方法。

以 BuildListenSocketMethod 为例,它规定了构建监听套接字的基本流程:先调用 CreateSocketOrDie 创建套接字,接着调用 BindSocketOrDie 绑定端口,最后调用 ListenSocketOrDie 进行监听。这个流程是固定的,不可更改。

TcpSocket 类继承自 Socket 类,并实现了 Socket 类中定义的所有纯虚函数。这意味着 TcpSocket 类提供了这些基本套接字操作步骤的具体实现。

通过这种方式,模板方法模式将套接字操作的通用流程(模板方法)与具体的实现细节(子类中的具体函数实现)分离开来。

这样做有以下几个好处:

  1. 代码复用:Socket 类中定义的模板方法可以被不同的子类(如这里的 TcpSocket)复用,避免了重复编写相同的流程代码。

  2. 灵活性和可扩展性:如果需要支持新的套接字类型(比如 UdpSocket),只需要创建一个新的子类,实现 Socket 类中定义的纯虚函数,就可以按照新的方式来处理套接字操作,而不需要修改现有的代码结构。

  3. 流程的一致性:通过在 Socket 类中定义模板方法,确保了套接字操作的基本流程在不同的实现中保持一致。

例如,当我们想要创建一个基于 TCP 的套接字并进行监听时,只需要创建一个 TcpSocket 对象,然后调用 BuildListenSocketMethod 方法,就可以按照预定的流程完成操作,而无需关心每个具体步骤的实现细节。这使得代码更加简洁、易于理解和维护。

四、总结

模板方法模式是一种强大的设计模式,通过将算法的骨架与具体实现分离,提高了代码的复用性、灵活性和可维护性。在实际开发中,当遇到具有固定流程但部分步骤需要定制的情况时,模板方法模式是一个很好的选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1957675.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

lora微调Qwen模型全流程

LoRA 微调 Qwen 模型的技术原理概述 LoRA&#xff08;Low-Rank Adaptation&#xff09;是一种用于大模型高效微调的方法。通过对模型参数进行低秩分解和特定层的微调&#xff0c;LoRA 能在保持模型性能的前提下显著减少训练所需的参数量和计算资源。接下来是对 LoRA 微调 Qwen…

Matlab编程资源库(9)数据插值与曲线拟合

一、一维数据插值 在MATLAB中&#xff0c;实现这些插值的函数是interp1&#xff0c;其调用格式为&#xff1a; Y1interp1(X,Y,X1,method) 函数根据X,Y的值&#xff0c;计算函数在X1处的值。X,Y是两个等长的已知向量&#xff0c;分别描述采样点和样本值&#xff0c;X1是一个向量…

【机器学习基础】初探机器学习

【作者主页】Francek Chen 【专栏介绍】⌈Python机器学习⌋ 机器学习是一门人工智能的分支学科&#xff0c;通过算法和模型让计算机从数据中学习&#xff0c;进行模型训练和优化&#xff0c;做出预测、分类和决策支持。Python成为机器学习的首选语言&#xff0c;依赖于强大的开…

vue项目引入live2d保姆级教程--web端、多种方法

一、自建live2d运行 1、选择SDK——live2d Cubism SDK &#xff08;1&#xff09;链接&#xff1a;Live2D Cubism SDK | Live2D Cubism 打开网站&#xff0c;它长这样&#xff1a; &#xff08;2&#xff09;选择web &#xff0c;到下个页面 &#xff08;3&#xff09;下…

Power Tower

Problem - D - Codeforces 牛客和codeforce都有 递归处理l,r&#xff0c;终点是lr && mod1 用扩展欧拉定理 // Problem: D. Power Tower // Contest: Codeforces - Codeforces Round 454 (Div. 1, based on Technocup 2018 Elimination Round 4) // URL: https://c…

Linux基础操作指令

Linux的操作特点&#xff1a;纯命令行&#xff08;虽然也有图形化界面&#xff0c;但主要是工程师使用&#xff0c;意义不大&#xff09; windows的操作特点&#xff1a;图形化界面&#xff08;也有纯命令行的形式&#xff0c;但其更贴近大众&#xff0c;命令行学习成本高&…

用Python打造精彩动画与视频1.2 安装Python和基本配置

1.2 安装Python和基本配置 在本章节中&#xff0c;我们将介绍如何在不同操作系统上安装Python&#xff0c;并进行基本配置&#xff0c;以便你能够顺利开始使用Python进行编程和多媒体创作。 1.2.1 Python的安装 Python有多个版本&#xff0c;目前主要使用Python 3版本。以下…

贪心加暴力枚举

数据范围只有 1 0 5 10^5 105所以我们可以直接暴力枚举&#xff0c;然后我们知道假操作一定是在最开始进行&#xff0c;至于加多少次我们可以直接枚举 class Solution { public:int minOperations(int k) {// 只可能先加后叠加if (k 1) return 0;int ans 0x7ffffff;int now…

Windows FreeCAD 导入ODA File Converter 插件

0. 背景 需要打开.dwg 格式的文件&#xff0c;AutoCAD 又要收费&#xff0c;所以使用法国的 FreeCAD&#xff0c; 安装完成打开.dwg 格式的文件时&#xff0c;出现问题如图所示&#xff1a; 1.下载插件 插件 ODA File Converter 网址 windows 插件在最下面&#xff1a; 2…

Android 性能优化(二):LeakCanary【用于分析代码是否存在内存泄漏】程序无响应

目录 1&#xff09;内存相关的五种常见问题 2&#xff09;内存溢出和内存泄漏 3&#xff09;LeakCanary是什么? 4&#xff09;LeakCanary如何使用&#xff0c;如何分析&#xff1f; 5&#xff09;LeakCanary监测的内容 提问&#xff1a;程序有时候很卡&#xff0c;经常会出现…

再论pg归档日志的设置

用过ORACLE的朋友&#xff0c;第一次设置 PG的归档参数&#xff0c;如下&#xff1a; 。。。 wal_level replica archive_mode on archive_command test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f 。。。 对于归档&#xff0c;还要用…

ConvGRU原理与开源代码

ConvGRU 1. 算法简介与应用场景2. 算法原理2.1 GRU基础2.2 ConvGRU原理2.2.1 ConvGRU的结构2.2.2 卷积操作的优点 2.3 GRU与ConvGRU的对比分析2.4 ConvGRU的应用 3. PyTorch代码 仅需要网络源码的可以直接跳到末尾即可 需要ConvLSTM的可以参考我的另外一篇博客&#xff1a;小白…

Halcon Blob分析

斑点分析的思路&#xff1a;在图像中&#xff0c;相关对象的像素可以通过其灰度值来识别。例如下图的组织颗粒。这些颗粒是凉的&#xff0c;而液体是暗的&#xff0c;通过选择明亮像素(阈值)&#xff0c;可以很容易地检测到颗粒。在需要应用中&#xff0c;这种简单的暗像素和亮…

成像光学:LCD的工作原理与结构图解

一、主流显示面板技术&#xff1a;LCD&#xff0c;OLED&#xff0c;MicroLED 二、主流显示屏的发展趋势 三、LCD堆叠结构&#xff08;以比较流行的TFT-LCD为例&#xff09; 沿光路方向介绍&#xff1a;背光&#xff0c;下偏光片&#xff08;polarizer&#xff09;&#xff0c;…

python实现图像分割算法2

python实现随机步行算法 随机步行算法数学模型Python 实现详细解释优缺点应用领域随机步行算法是一种常用于图像分割和图像分析的算法。它通过模拟随机游走来确定图像中每个像素的标签或类别。随机步行算法特别适合用于解决有种子标记的图像分割问题,其中用户提供一些初始标记…

【Python】基础语法(上)

本篇文章讲解以下知识&#xff1a; &#xff08;1&#xff09;初始编码 &#xff08;2&#xff09;输出 &#xff08;3&#xff09;初识数据类型 一&#xff1a;初识编码 在计算机中所有的数据本质上都是以0和1的组合来存储。 比如&#xff1a;在一个文件中有以下内容&am…

力扣SQL50 上级经理已离职的公司员工 一题双解

Problem: 1978. 上级经理已离职的公司员工 Code -- 方法 1 -- select e1.employee_id -- from employees e1 -- left join employees e2 -- on e1.manager_id e2.employee_id -- where e1.salary < 30000 -- and e1.manager_id is not null -- and e2.employee_id is…

SpringBoot 整合 Redis 实现验证码登录功能

一、整合Redis 在pom.xml中添加Redis相关依赖&#xff1b; <!--Spring Data Redis依赖配置--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency>…

103.qt qml-最全Table新增下拉复制功能

在上篇文章102.qt qml-最全Table交互之多列固定、行列拖拽、自定义委托、标题交互使用教程_qt 表格控件 拖动列-CSDN博客 我们实现了大部分功能,所以本章实现下拉复制功能。 demo截图如下所示: 支持跨界复制,如果下拉的位置大于Table则会动画向下移动,具体可以参考视频链接…

颠覆未来计算!CRAM技术摒弃冯·诺依曼模型,20年研究终迎突破

未来科技&#xff1a;AI计算需求激增&#xff0c;数据中心耗电量堪比派对狂饮&#xff01;明尼苏达大学研究团队或携革命性设备&#xff0c;以惊人能效解决AI能耗难题&#xff01; 研究人员设计了一种新型的"计算随机存取存储器"&#xff08;CRAM&#xff09;原型芯…