目录
一、模板方法模式的定义与结构
二、模板方法模式的优点
三、模板方法模式的示例
示例一:
示例二:
四、总结
在软件开发中,设计模式是解决常见问题的可复用方案。其中,模板方法模式是一种行为型设计模式,它在不改变算法结构的情况下,允许子类重新定义算法中的某些步骤。
一、模板方法模式的定义与结构
模板方法模式由两部分组成:抽象类和具体子类。
抽象类:定义了模板方法和一系列抽象方法。模板方法封装了算法的骨架,包含了按照特定顺序调用的抽象方法和其他已经实现的方法。
具体子类:继承自抽象类,实现抽象类中的抽象方法,从而完成特定的算法步骤。
二、模板方法模式的优点
- 代码复用:将算法的通用部分封装在模板方法中,减少了重复代码的编写。
- 灵活性:允许子类在不改变算法结构的前提下,定制特定的步骤。
- 可维护性:清晰的结构使得代码更易于理解和维护。
三、模板方法模式的示例
示例一:
下面是一个简单的示例,假设我们要实现不同类型的文件读取操作。
#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
和抽象方法 OpenFile
、ReadContents
、CloseFile
。TextFileReader
和 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
类是一个抽象基类,它定义了一系列纯虚函数,如CreateSocketOrDie
、BindSocketOrDie
、ListenSocketOrDie
等。这些函数代表了套接字操作的基本步骤,但具体的实现细节并未给出。同时,
Socket
类中还定义了一些非纯虚函数,如BuildListenSocketMethod
、BuildConnectSocketMethod
和BuildNormalSocketMethod
,这些函数构成了模板方法。以
BuildListenSocketMethod
为例,它规定了构建监听套接字的基本流程:先调用CreateSocketOrDie
创建套接字,接着调用BindSocketOrDie
绑定端口,最后调用ListenSocketOrDie
进行监听。这个流程是固定的,不可更改。
TcpSocket
类继承自Socket
类,并实现了Socket
类中定义的所有纯虚函数。这意味着TcpSocket
类提供了这些基本套接字操作步骤的具体实现。通过这种方式,模板方法模式将套接字操作的通用流程(模板方法)与具体的实现细节(子类中的具体函数实现)分离开来。
这样做有以下几个好处:
代码复用:
Socket
类中定义的模板方法可以被不同的子类(如这里的TcpSocket
)复用,避免了重复编写相同的流程代码。灵活性和可扩展性:如果需要支持新的套接字类型(比如
UdpSocket
),只需要创建一个新的子类,实现Socket
类中定义的纯虚函数,就可以按照新的方式来处理套接字操作,而不需要修改现有的代码结构。流程的一致性:通过在
Socket
类中定义模板方法,确保了套接字操作的基本流程在不同的实现中保持一致。例如,当我们想要创建一个基于 TCP 的套接字并进行监听时,只需要创建一个
TcpSocket
对象,然后调用BuildListenSocketMethod
方法,就可以按照预定的流程完成操作,而无需关心每个具体步骤的实现细节。这使得代码更加简洁、易于理解和维护。
四、总结
模板方法模式是一种强大的设计模式,通过将算法的骨架与具体实现分离,提高了代码的复用性、灵活性和可维护性。在实际开发中,当遇到具有固定流程但部分步骤需要定制的情况时,模板方法模式是一个很好的选择。