[封装02-设计模式] 命令模式 享元模式 组合模式 代理模式 策略模式

468 阅读8分钟

导航

[react] Hooks

[封装01-设计模式] 设计原则 和 工厂模式(简单抽象方法) 适配器模式 装饰器模式
[封装02-设计模式] 命令模式 享元模式 组合模式 代理模式

[React 从零实践01-后台] 代码分割
[React 从零实践02-后台] 权限控制
[React 从零实践03-后台] 自定义hooks
[React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
[React 从零实践05-后台] Gitlab-CI使用Docker自动化部署

[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend

[源码-vue06] Vue.nextTick 和 vm.$nextTick
[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI

[数据结构和算法01] 二分查找和排序

[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[深入21] 数据结构和算法 - 二分查找和排序
[深入22] js和v8垃圾回收机制
[深入23] JS设计模式 - 代理,策略,单例

[前端学java01-SpringBoot实战] 环境配置和HelloWorld服务
[前端学java02-SpringBoot实战] mybatis + mysql 实现歌曲增删改查
[前端学java03-SpringBoot实战] lombok,日志,部署
[前端学java04-SpringBoot实战] 静态资源 + 拦截器 + 前后端文件上传
[前端学java05-SpringBoot实战] 常用注解 + redis实现统计功能
[前端学java06-SpringBoot实战] 注入 + Swagger2 3.0 + 单元测试JUnit5
[前端学java07-SpringBoot实战] IOC扫描器 + 事务 + Jackson

(一) 前置知识

(1)前置知识

scaffolding 脚手架
cluster 集群
handler 处理
Typography 排版

invoke 调用
maintainer 维护者
fly 苍蝇
flyweight 享元模式
recover 回收

pattern 模式
design pattern 设计模式
strategy 策略模式
singleton 单列模式

expires 过期

(2)layout

xs => extra small 超小
sm => small
md => medium 中等的 中号的
lg => large
xl => extra large 超大

(3) pro-table

Protable => search={false}自定义查找

(4) 本地分支关联远程分支

git branch --set-upstream-to=origin/remote_branch your_branch

(二) 命令模式

  • 命令模式是一种高内聚的模式
  • 概念
    • 将一个请求封装成一个对象,从而让你使用 ( 不同的请求将客户端参数化 ),( 对请求排队或者记录日志 ),可以提供命令的撤销或恢复功能
    • 命令模式的核心在于引入了 ( 命令类 )
  • 角色
    • 调用者/发布者 invoker => 发布命令,调用命令对象,对reciver不可见,不知道谁执行和如何执行
    • 接收者/执行者 receiver => 执行命令,提供对应接口来处理请求
    • 命令对象 command => 命令对象,调用 ( 接收者对应的接口 ) 来处理 ( 发布者的请求 )
  • 特点
    • invoker 和 reciver 相互独立,将请求封装成command,请求的具体执行是 ( command对象调用reciver提供的接口来执行 )
    • ( 命令对象command ) 就是 ( 调用者invoker ) 和 ( 接收者reciver ) 的桥梁,解耦调用者和接收者
// 命令模式

    // Invoker 发布者
    // 1. 发布者中有 命令对象
    // 2. 发布者发布命令,即调用命令对象中的方法,而命令对象又会去调用接收者的方法
    class Invoker {
      constructor(command) {
        this.command = command
      }
      invoke = () => {
        this.command.execute()
      }
    }

    // Command 命令对象
    // 1. 命令对象中有 接收者
    // 2. 命令对象 调用接收者中的方法
    class Command {
      constructor(receiver) {
        this.receiver = receiver
      }
      execute() {
        this.receiver.execute()
      }
    }

    // Receiver 接收者
    // 接收者 执行最终的请求
    class Receiver {
      execute() {
        console.log('接收者执行方法')
      }
    }

    const cook = new Receiver() // 厨师-接收者-做饭
    const shop = new Command(cook) // 商店-命令对象-命令厨师做饭
    const user = new Invoker(shop) // 客户-调用者-发布命令
    user.invoke()

(三) 享元模式 flyweight pattern

  • 利用共享技术来减少创建对象的数量,从而减少内存占用,提高性能
  • ( 享元模式 ) 提醒我们,将一个 ( 对象的属性 ) 划分为 ( 内部状态 ) 和 ( 外部状态 )
    • 内部状态:被对象集合共享,通常不会改变
    • 外部状态:根据应用场景经常改变
  • 享元模式是 ( 用时间换空间 )
  • 应用场景
    • 只要是需要大量创建 ( 重复的类的代码 ),均可以使用享元模式抽离 ( 内部状态和外部状态 ),从而减少重复类的创建
  • 相关单词
    • flyweight
    • fly是苍蝇的意思
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <input type="radio" value="red" name="color" checked> 红色
  <input type="radio" value="yellow" name="color"> 黄色
  <input type="radio" value="blue" name="color"> 蓝色
  <button onclick={draw()}>绘制</button>
  <div id="container">颜色</div>
  <script>
    // 享元模式
    class FlyweightDiv {
      constructor() {
        this.element = document.createElement('div') // ----- 内部属性,不变
      }
      setColor = (color) => { // ---------------------------- 外部外部,变化
        this.element.style=`width: 100px; height: 100px; background-color:${color}`
      }
    }
    const myDiv = new FlyweightDiv()
    const draw = () => {
      const btns = Array.from(document.getElementsByName('color'))
      const checkedBtn = btns.find(btn => btn.checked)
      const color = checkedBtn?checkedBtn.value:'red'
      console.log(`color`, color)
      myDiv.setColor(color)
      document.getElementById('container').appendChild(myDiv.element)
      // 注意:这里每次都是同一个element,所以不会新添加
    }
  </script>
</body>
</html>

(四) 组合模式 compose pattern

  • 将 ( 对象组合成树形结构 ),以表示 ( 部分-整体 ) 的层次结构
  • 客户可以使用统一的方式,对待 ( 组合对象 ) 和 ( 叶子对象 )
  • 又称整体-部分模式
  • 关键词
    • 部分/整体
    • 叶子对象/组合对象
  • 特点
    • 树叶形结构,是部分/整体的层次结构
    • 一致操作性,( 树对象和叶对象 ) 对外接口保持一致,即 ( 操作与数据结构一致 )
    • 自上而下的请求流行,从 ( 树对象传递给叶对象 )
    • 调用顶层对象,会自行遍历其下的叶对象执行 image.png

image.png

实现一个树形文件结构
- folder 文件夹
- file 文件
- 文件夹下可以创建文件,文件不能再创建文件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      // 组合模式
      // 1. Floder 树对象
      // 2. File 叶对象
      // 3. Folder 和 File 接口统一,都具有 add 和 scan 方法
      class Floder {
        constructor(name) {
          this.name = name;
          this.files = [];
        }
        add = (file) => {
          this.files.push(file);
        };
        scan = () => {
          this.files.forEach((file) => file.scan());
        };
      }

      class File {
        constructor(name) {
          this.name = name
        }
        add = () => {
          throw new Error('文件下面不能添加文件,文能在文件夹下添加文件')
        }
        scan = () => {
          console.log(`正在扫描文件: ${this.name}`)
        }
      }

      const carFolder = new Floder('车')
      const bmwFile = new File('宝马')
      carFolder.add(bmwFile)
      carFolder.scan()
    </script>
  </body>
</html>

(五) 复习 - 代理模式 proxy pattern

  • 定义
    • 给 ( 一个对象 ) 提供一个 ( 代理对象 ),并由 ( 代理对象 ) 来控制 ( 原对象的引用 ),代理对象就类似于 ( 中介 )
  • 角色
    • 客户对象
    • 代理对象
    • 委托对象
    • ( 客户对象 ) === ( 代理对象 ) === ( 委托对象 )
  • 特点
    • ( 代理对象 ) 本身不提供服务,而是通过调用 ( 委托对象 ) 的相关的方法来提供特定的服务,即 ( 真正的业务功能还是由委托对象 ) 来实现
    • ( 代理对象 ) 主要负责委托对象的 ( 预处理消息,过滤消息,转发消息 )
    • ( 代理类 ) 除了是 ( 客户类 ) 和 ( 委托类 ) 的中介之外,还可以给代理类增加额外的功能来扩展委托类的功能,这样我们就可以直接修改代理类而不是直接修改委托类,符合代码设计的开闭原则

实战

(5-1) 代理模式 - ajax请求添加缓存功能

  • 原理
    • ( 缓存 ) 每次请求的 ( 参数 ) 和 ( 返回值 ),如果参数一样,就直接返回 ( map ) 中参数对应的返回值
    • 利用 ( 代理模式 ) 给原有的函数添加新的逻辑但又不影响原函数,相当于用 ( 函数组合 ) 来实现 ( 复用逻辑和添加逻辑 )
    • 记得对比我之前的文章
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      // 代理模式

      // 正常的请求
      const request = (params) => {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            return resolve(`data: ${params}`);
          }, 1000);
        });
      };

      // 代理模式 - 代理请求
      const proxyRequest = (params) => {
        return cache(params);
      };

      // map 用来做参数和返回值的映射
      const mapInstance = new Map();

      // 利用map实现参数和返回值的缓存
      const cache = async (params) => {
        // 参数在map中存在,直接返回参数对应的请求结果
        if (mapInstance.has(params)) {
          return mapInstance.get(params);
        }
        // 参数在map中不存在,则请求并做参数和请求返回值的映射
        // 所以:当参数存在时,直接从map中返回,而不是重新请求
        console.log("在本例中,我只会执行一次");
        const res = await request(params);
        mapInstance.set(params, res);
        return res;
      };

      proxyRequest("参数").then((res) => console.log(`res`, res));
      setTimeout(() => {
        proxyRequest("参数").then((res) => console.log(`res`, res));
      }, 2000);
    </script>
  </body>
</html>

image.png

(5-2) 代理模式 - 代理缓存,处理缓存过期时间

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      class Storage {
        constructor(type, expires) {
          this.storage = window[type];
          this.expires = expires;
        }

        // 存
        // 代理原生的 setItem() 方法,添加缓存过期时间
        setItem = (key, value) => {
          this.storage.setItem(
            key,
            JSON.stringify({
              value,
              setTime: +new Date(), // --------------------- ( 存 ) 的时间戳
            })
          );
        };

        // 取
        // 代理原生的 getItem() 方法,根据传入的过期时间,来判断数据是否过期
        getItem = (key) => {
          const now = +new Date(); // --------------------- ( 取 ) 的时间戳
          const { value, setTime } = JSON.parse(this.storage.getItem(key));
          if (now - setTime > this.expires) {
            this.storage.removeItem(key); // -------------- 过期删除
          }
          return this.storage.getItem(key);
        };
      }

      const localStorageInstance = new Storage("localStorage", 5000);
      localStorageInstance.setItem("name", "woow_wu7");

      console.log("未过期", localStorageInstance.getItem("name"));

      setTimeout(() => {
        console.log("过期", localStorageInstance.getItem("name"));
      }, 6000);
  
    </script>
  </body>
</html>

image.png

(六) 复习 - 策略模式 strategy pattern

  • 概念
    • 定义一系列算法,把他们一个个封装起来,并使他们可以 ( 相互替换 )
    • 将 ( 不变的部分 ) 和 ( 变化的部 ) 分隔开,有点类似于 ( 享元模式 )
    • ( 策略模式 ) 的主要目的就是将 ( 算法的使用 ) 和 ( 算法的实现 ) 分离开来
  • 成员
    • ( 策略模式 ) 主要包括 ( 策略类 ) 和 ( 环境类 )
    • 策略类
      • 封装了具体的算法,并负责具体的计算过程
    • 环境类
      • ( 环境类 ) context负责 ( 接受客户的请求 ),随后把 ( 请求委托给某一个策略类 )
  • 优点
    • 避免多重选择语句出现:策略模式利用 ( 组合 委托 多态 ) 等技术和思想,可以有效的避免 ( 多重条件选择语句,比如if...else或者switch的出现 )
    • 符合开放封闭的设计原则:即可扩展不可修改,将算法封装在独立的 strategy 策略中,使得他们容易切换,理解,扩展
    • 算发可复用:可以服用在系统其他地方,从而避免冗余重复的工作
  • 缺点
    • 必须了解所有的策略,必须了解各个策略的不同点,才能实现一个特点的策略

实战

(6-1) 计算奖金

  • 规则
    • S绩效 - 4倍工资
    • A绩效 - 3倍工资
    • B绩效 - 2倍工资
  • 一些单词
    • bonus 奖金
    • salary 工资
    • performance 绩效 性能
    • strategy pattern 策略模式
  • 我之前的关于策略模式的文章
(1) 不做任何优化的写法
- 缺点
  - 有很多if...else
  - 缺乏扩展性:如果要添加C绩效,就得修改内部的函数实现,违反了开放封闭原则即可扩展但 ( 不可修改 )
  - 复用性差
- bonus 奖金
- performance 绩效
- salary 工资
const getBonus = (performance, salary) => {
    if (performance === 'S') return 4 * salary;
    if (performance === 'A') return 3 * salary;
    if (performance === 'B') return 2 * salary;
}
getBonus('A', 1000) // 输出 3000



-------
(2) 使用 策略模式 strategy pattern 重构代码
- 优点
  - 避免多个if...esle
  - 符合开放/封闭原则,扩展不需要修改原来的逻辑,也不会影响原来的逻辑
  - 所有计算奖金bonus的逻辑都不在getBonus函数即环境类context中,而是分布在各个策略对象strategyPattern中
  - context环境类不负责计算,只是负责将请求委托给strategyPattern策略类
const strategyPattern = {
  S: (salary) => 4 * salary,
  A: (salary) => 3 * salary,
  B: (salary) => 1 * salary,
} 
const getBonus = (performance, salary) => strategyPattern[performance](salary)
getBonus('A', 1000) // 输出3000

(6-2) 表单验证

  • 不做任何优化
(1) 不做任何优化

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <form action="javascript:void(0)" method="post">
    <input type="text" name="username">
    <input type="text" name="password">
    <button>提交表单</button>
  </form>
  <script>
    const form = document.querySelector('form')
    form.onsubmit = (e) => {
      if (form.username.value === '') {
        console.log('用户名不能为空')
        return false
      }
      if (form.password.value.length < 6) {
        console.log('密码不能少于6位')
        return false
      }
      console.log('提交成功')
      return true
    }
  </script>
</body>
</html>

image.png

(2) 使用策略模式优化 - 表单验证

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <form action="javascript:void(0)" method="post">
      <input type="text" name="username" />
      <input type="text" name="password" />
      <button>提交表单</button>
    </form>
    <script>
      const form = document.querySelector("form");

      // strategy 策略对象
      const strategy = {
        notEmpty: (value, err) => { if (!value) return err },
        minLength: (value, length, err) => { if (value.length < 6) return err}
      }

      // Validator 环境类
      class Validator {
        constructor() {
          this.rules = [];
        }
        add = (value, rule, err) => { // --------------- add()方法的返回值是 err | undefined
          const ruleArr = rule.split(":"); // ---------- [notEmpty] 或 [minLength, 6]
          this.rules.push(() => { // ------------------- shift unshift push 改变原数组
            const strategyKey = ruleArr.shift(); // ---- 'notEmpty' 或 'minLength'
            ruleArr.unshift(value) // ------------------ 头部添加
            ruleArr.push(err) // ----------------------- 尾部添加
            // 1. ruleArr = [value, err] 或 [value, minLength, err]
            // 2. 这里 apply 用得很秒,因为 ruleArr 是一个数组
            return strategy[strategyKey].apply(null, ruleArr)
          });
        };
        start = () => this.rules.map(rule => rule()) // start()返回一个err数组
      }

      form.onsubmit = (e) => {
        e.preventDefault();
        const validator = new Validator();
        validator.add(form.username.value, "notEmpty", "用户名不能为空"); //  add()返回 err | undefined
        validator.add(form.password.value, "minLength:6", "密码不能少于6位"); // add()返回 err | undefined
        const errArr = validator.start() // start()返回err数组,成员可能是undefined表示通过验证
        const filterErrArr = errArr.filter(err => err) // 过滤掉通过验证的成员
        filterErrArr.forEach(err => console.log(err)) // 打印错误
        if (filterErrArr.length>0) return false // 如果过滤掉通过验证成员后,数组长度大于0,则表示有未通过验证的错误,返回false
      };
    </script>
  </body>
</html>

image.png

资料

命令模式(精简) juejin.cn/post/684490…
享元模式 techblog.sishuxuefu.com/atricle.htm…
享元模式 juejin.cn/post/684490…
组合模式 juejin.cn/post/684490…
代理模式[我的掘金] juejin.cn/post/691874…