装饰者模式

前言

在传统的面向对象语言中,给对象添加功能通常使用继承方式。这种方式并不灵活且存在一些问题:

随着业务复杂度增加,会创建出大量的子类,且会导致父子类之间存在强耦合性。

装饰者(decorator)模式,能够在不改变原始对象的基础上,在运行时动态地添加、删除或修改对象的功能。与继承相比,装饰者是一种更轻便灵活的做法。

当然前端也可实现装饰类,但前端中类本质上是构造函数的一种语法糖,此处不做讨论。

举个栗子

在日常项目中,我们会遇到这样的问题:有一个新需求,要在已有功能基础上增加一些内容。但老代码可能不是我们编写的,此时我们会面临如下问题

  • 不知道老代码的使用范围,随意改动容易影响之前的功能
  • 看懂老代码的逻辑再进行修改,比较耗时

我们来看一个例子:假设项目中有一个函数,在多个地方被调用,现在需要在调用这个函数前加一些逻辑。

// 原始函数
function oriFn(){console.log('执行原始函数');
}
// 找到原函数进行修改
function oriFn(){console.log('执行前置逻辑');console.log('执行原始函数');
}

当然在原函数足够简单时可以这么处理,如果原函数非常复杂,就需要考虑在不改变原函数的情况下,新增所需功能,一般做如下处理

let copyFn = oriFn;
oriFn = function(){console.log('执行前置逻辑');copyFn();
}

这种方式符合封闭开放原则,且未改动原函数。但其也存在一些问题

  • 必须新增一个临时变量,如果多次包装会产生多个非必要的变量
  • this指向问题,部分场景下需要手动处理(如document等)

AOP装饰函数

AOP(面向切面编程)。主要作用是把一些跟核心业务逻辑无关的功能抽离出来,再通过“动态织入”的方式掺入业务逻辑模块中,保证了业务逻辑模块的纯净和高内聚性。

// 前置装饰
Function.prototype.before = function(beforeFn){let _this = this;return function(){beforeFn.apply(this, arguments);return _this.apply(this, arguments);}
}
// 后置装饰
Function.prototype.after = function(afterFn){let _this = this;return function(){var ret = _this.apply(this, arguments);afterFn.apply(this, arguments);return ret;}
}

所以上面的例子可以改写为

// 定义前置函数
function beforeFn(){console.log('执行前置逻辑')
}
// 定义后置函数
function afterFn(){console.log('执行后置逻辑')
}oriFn = oriFn.before(beforeFn).after(afterFn);
oriFn();
// 输出:执行前置逻辑
// 输出:执行原始函数
// 输出:执行后置逻辑

使用场景

通常用于日记统计、安全控制、异常处理等与业务逻辑无关的场景下。

便于理解,这里举个常见的例子:在表单提交前要做必填和数据校验,通常写法如下

// 校验
function validate() {if(xxx){console.log('校验失败')return false}return true
}
// 点击提交
function submit() {// 校验if(!validate()){return}// 发送请求console.log('发送请求')fecth('http://xxx.com/xxx')
}

如果此时有如下需求

  • 需要动态决定需不需要校验

  • 需要统计按钮点击次数

  • 需要上报用户各个行为做前端监控

按照一般写法就需要找到各个业务方法入口,不停地添加判断条件,这样做就会导致各个功能模块耦合严重,所以我们可以这么处理

Function.prototype.before = function (beforefn) {let self = thisreturn function () {if (beforefn.name=='validate') {// 前置校验函数结果为false,不执行后续if(beforefn.apply(this, arguments) === false){return}}else{beforefn.apply(this, arguments)}return self.apply(this, arguments)}
}
// 统计函数
function getClickNum(){let _this = this;_this.num = 0;_this.addNum = function(){_this.num++;}
}
let clickFn = new getClickNum()
// 日志输出
function log() {console.log('用户点击了提交按钮')console.log('点击按钮次数:',clickFn.num)
}// 直接调用
submit()
// 需要校验
submit = submit.before(validate)
// 需要统计
submit = submit.before(clickFn.addNum)
// 需要统计+日志
submit = submit.before(clickFn.addNum).after(log)
// 需要校验+统计+日志
submit = submit.before(validate).before(clickFn.addNum).after(log)

这样就可以做到各功能解耦,插拔式处理业务逻辑。

优缺点

优点

  • 符合开闭原则:在不修改已有代码逻辑的情况下扩展新的功能
  • 单一职责原则:将不同的功能实现封装到不同的装饰器中,每个装饰器功能确定,便于维护,且整体代码结构清晰
  • 动态组合:可根据不同的需求场景,通过组装不同装饰器实现功能

缺点

  • 增加复杂度:装饰者模式引入了额外的类和对象,某些情况下会占用较多的内存,且在使用时需要考虑装饰器之间的关系和顺序。
  • 增大运行时开销:每个装饰器都会增加一次方法调用的开销,会产生一定的性能损失。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FairGuard游戏加固全面适配纯血鸿蒙NEXT

2024年10月8日,华为正式宣布其原生鸿蒙操作系统 HarmonyOS NEXT 进入公测阶段,标志着其自有生态构建的重要里程碑。 作为游戏安全领域领先的第三方服务商,FairGuard游戏加固在早期就加入了鸿蒙生态的开发,基于多项独家技术与十余年…

数据库权限提升GetShell

数据库提权总结 - 随风kali - 博客园 (cnblogs.com) MySQL 漏洞利用与提权 | 国光 (sqlsec.com) sql注入getshell的几种方式 第99天:权限提升-数据库提权&口令获取&MYSQL&MSSQL&Oracle&MSF SQL注入拿shell的方式应该是通用的得到连接数据库…

未来AI的学习能力会达到怎样的水平?

​ 大家好,我是Shelly,一个专注于输出AI工具和科技前沿内容的AI应用教练,体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具,拥抱AI时代的到来。 AI工具集1:大厂AI工具【共2…

微软运用欺骗性策略大规模打击网络钓鱼活动

微软正在利用欺骗性策略来打击网络钓鱼行为者,方法是通过访问 Azure 生成外形逼真的蜜罐租户,引诱网络犯罪分子进入以收集有关他们的情报。 利用收集到的数据,微软可以绘制恶意基础设施地图,深入了解复杂的网络钓鱼操作&#xff…

Verilog基础:层次化标识符的使用

相关阅读 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html?spm1001.2014.3001.5482 一、前言 Verilog HDL中的标识符(identifier)是一个为了引用而给一个Verilog对象起的名字,分为两大类:普通标识符大类和层次化标识符大类。…

监控易监测对象及指标之:Kafka中间件JMX监控指标解读

监控易作为一款功能强大的监控软件,旨在为企业提供全方位的IT系统监控服务。其中,针对Kafka中间件的JMX监控是监控易的重要功能之一。本文将详细解读监控易中Kafka的JMX监控指标,帮助企业更好地理解并运用这些数据进行系统性能调优和故障排查…

算法笔记day07

1.最长回文子串 最长回文子串_牛客题霸_牛客网 算法思路: 使用中心扩散算法,枚举所有的中点,向两边扩散,一个中点需要枚举两次,一次当回文串是奇数另一次回文串是偶数的情况。 class Solution { public:int getLong…

mysql--基本查询

目录 搞定mysql--CURD操作,细节比较多,不难,贵在多多练 1、Create--创建 (1)单行插入 / 全列插入 (2)插入否则替换 (3)替换 2、Retuieve--select 1)全…

git rebase的常用场景: 交互式变基, 变基和本地分支基于远端分支的变基

文章目录 作用应用场景场景一:交互式变基(合并同一条线上的提交记录) —— git rebase -i HEAD~2场景二:变基(合并分支) —— git rebase [其他分支名称]场景三:本地分支与远端分支的变基 作用 使git的提交记录变得更加简洁 应用场景 场景…

Unity之如何使用Unity Cloud Build云构建

文章目录 前言什么是 UnityCloudBuild?如何使用Unity云构建Unity 团队中的人员不属于 Unity Team 的人员UnityCloudBuild2.0价格表如何使用Unity云构建配置CloudBuild前言 Unity Cloud Build作为Unity平台的一项强大工具,它允许开发团队通过云端自动构建项目,节省了繁琐的手…

基于Springboot在线视频网站的设计与实现

基于Springboot视频网站的设计与实现 开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7 数据库工具:Navicat11 开发软件:idea 源码获取:https://do…

JSON 注入攻击 API

文章目录 JSON 注入攻击 API"注入所有东西"是"聪明的"发生了什么? 什么是 JSON 注入?为什么解析器是问题所在解析不一致 JSON 解析器互操作性中的安全问题处理重复密钥的方式不一致按键碰撞响应不一致JSON 序列化(反序列化)中的不一致 好的。JSON 解析器…

Java | Leetcode Java题解之第497题非重叠矩形中的随机点

题目&#xff1a; 题解&#xff1a; class Solution {Random rand;List<Integer> arr;int[][] rects;public Solution(int[][] rects) {rand new Random();arr new ArrayList<Integer>();arr.add(0);this.rects rects;for (int[] rect : rects) {int a rect[0…

PPT自动化:Python如何将PPT转换为图片(ppt2img源码)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 PPT转换图片 📒📝 Windows环境下PPT转为图片(源码)📝 Linux环境下PPT转为图片(源码)📝 注意事项⚓️ 相关链接 ⚓️📖 介绍 📖 在日常工作中,我们常常需要将大量的幻灯片转换为图片,这样不仅方便分享,还能提…

【数据库系统概论】第3章 关系数据库标准语言SQL(一)数据定义(超详细)

教材&#xff1a; 数据库系统概论&#xff08;第6版&#xff09;王珊,杜小勇,陈红编著 目录 一、SQL概述 1.1 SQL 的产生与发展 1.2 SQL的特点 1.3 SQL的基本概念 二、数据定义 2.1 数据库的定义 2.2 数据表的定义 2.3 模式的定义 一、SQL概述 1974年IBM为关系DBMS设…

Docker 搭建mysql

拉取mysql镜像 docker pull mysql # 拉取镜像 [rooteason ~]# docker pull mysql Using default tag: latest latest: Pulling from library/mysql 72a69066d2fe: Pull complete 93619dbc5b36: Pull complete 99da31dd6142: Pull complete 626033c43d70: Pull complete 37d…

用HTML构建酷炫的文件上传下载界面

1. 基础HTML结构 首先&#xff0c;我们构建一个基本的HTML结构&#xff0c;包括一个表单用于文件上传&#xff0c;以及一个列表用于展示已上传文件&#xff1a; HTML <!DOCTYPE html> <html> <head><title>酷炫文件上传下载</title><link …

分布式ID生成策略

文章目录 分布式ID必要性1.UUID2.基于DB的自增主键方案3.数据库多主模式4.号段模式5.Redis6.Zookeeper7.ETCD8.雪花算法9.百度(Uidgenerator)10.美团(Leaf)11.滴滴(TinyID) 分布式ID必要性 业务量小于500W的时候单独一个mysql即可提供服务&#xff0c;再大点的时候就进行读写分…

浏览器播放rtsp视频流解决方案

方案一: html5 websocket_rtsp_proxy 实现视频流直播 实现原理 实现步骤 服务器安装streamedian服务器 客户端通过video标签播放 <video id"test_video" controls autoplay></video><script src"free.player.1.8.4.js"></script&g…

openresty通过header_filter_by_lua记录特定的请求头和特定的响应头到日志文件

有时我们希望记录特定的请求头信息和特定的响应头信息,以便能够通过关联请求信息和响应头信息,来实现记录请求和响应的对应关系。这里通过逐步尝试和优化的方式进行尝试。具体包括将需要的请求头和响应头组织到一条日志记录,输出到单独的错误日志文件记录等的配置尝试。 1.…