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

目录

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

二、模板方法模式的优点

三、模板方法模式的示例

示例一:

示例二:

四、总结


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

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

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

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

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

二、模板方法模式的优点

  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 方法,就可以按照预定的流程完成操作,而无需关心每个具体步骤的实现细节。这使得代码更加简洁、易于理解和维护。

四、总结

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

  • 16
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值