当前位置: 首页 > news >正文

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

目录

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

二、模板方法模式的优点

三、模板方法模式的示例

示例一:

示例二:

四、总结


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

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

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

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

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

二、模板方法模式的优点

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

四、总结

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 使用 Python 对雷达卫星 sar 图像进行降噪的三种方法
  • 使用PasteSpider实现类似Jenkins的功能,让你的2G服务器也可以飞起
  • Scrapy框架在处理大规模数据抓取时有哪些优化技巧?
  • Spring实现自定义注解
  • PHP开发【石头剪刀布小游戏】
  • 04-Fastjson反序列化漏洞
  • 麻雀搜索算法(SSA)与长短期记忆网络(LSTM)结合的预测模型(SSA-LSTM)的Python 和 MATLAB实现
  • 文档在线预览:keking/kkFileView踩坑记
  • 精通Perl代码优化:释放自定义优化技术的力量
  • 微软蓝屏事件:全球网络安全与系统稳定性的警示
  • Unity获取Animator动画播放完成事件
  • 第三十一天 chrome调试工具
  • 2023-2024年 Java开发岗面试题经验分享
  • ESP32是什么?
  • C++函数详解:全面指南
  • [NodeJS] 关于Buffer
  • 2017年终总结、随想
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • exports和module.exports
  • GDB 调试 Mysql 实战(三)优先队列排序算法中的行记录长度统计是怎么来的(上)...
  • Iterator 和 for...of 循环
  • Java精华积累:初学者都应该搞懂的问题
  • js继承的实现方法
  • JS进阶 - JS 、JS-Web-API与DOM、BOM
  • Redash本地开发环境搭建
  • ucore操作系统实验笔记 - 重新理解中断
  • -- 查询加强-- 使用如何where子句进行筛选,% _ like的使用
  • 翻译 | 老司机带你秒懂内存管理 - 第一部(共三部)
  • 近期前端发展计划
  • 可能是历史上最全的CC0版权可以免费商用的图片网站
  • 类orAPI - 收藏集 - 掘金
  • 如何打造100亿SDK累计覆盖量的大数据系统
  • 什么是Javascript函数节流?
  • 我是如何设计 Upload 上传组件的
  • 移动端唤起键盘时取消position:fixed定位
  • UI设计初学者应该如何入门?
  • ​LeetCode解法汇总307. 区域和检索 - 数组可修改
  • # SpringBoot 如何让指定的Bean先加载
  • #!/usr/bin/python与#!/usr/bin/env python的区别
  • #define、const、typedef的差别
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • #职场发展#其他
  • (~_~)
  • (1)常见O(n^2)排序算法解析
  • (Redis使用系列) SpirngBoot中关于Redis的值的各种方式的存储与取出 三
  • (论文阅读40-45)图像描述1
  • (四) Graphivz 颜色选择
  • (源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模
  • (转)机器学习的数学基础(1)--Dirichlet分布
  • (最优化理论与方法)第二章最优化所需基础知识-第三节:重要凸集举例
  • .NET CORE 第一节 创建基本的 asp.net core
  • .NET core 自定义过滤器 Filter 实现webapi RestFul 统一接口数据返回格式
  • .NET Core跨平台微服务学习资源
  • .net framwork4.6操作MySQL报错Character set ‘utf8mb3‘ is not supported 解决方法