• JS十大设计模式(2/2)


    六、适配器模式

    什么是适配器模式

    适配器模式的作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。

    适配器的别名是包装器(wrapper),这是一个相对简单的模式。在程序开发中有许多这样的场景:当我们试图调用模块或者对象的某个接口时,却发现这个接口的格式并不符合目前的需求。
    这时候有两种解决办法:

    第一种是修改原来的接口实现,但如果原来的模块很复杂,或者我们拿到的模块是一段别人编写的经过压缩的代码,修改原接口就显得太不现实了。
    第二种办法是创建一个适配器,将原接口转换为客户希望的另一个接口,客户只需要和适配器打交道。

    核心思想
    解决两个已有接口之间不匹配的问题

    具体实现

    // 渲染数据,格式限制为数组了
    function renderData(data) {
      data.forEach(function (item) {
        console.log(item);
      });
    }
    
    // 对非数组的进行转换适配
    function arrayAdapter(data) {
      if (typeof data !== "object") {
        return [];
      }
    
      if (Object.prototype.toString.call(data) === "[object Array]") {
        return data;
      }
    
      var temp = [];
    
      for (var item in data) {
        if (data.hasOwnProperty(item)) {
          temp.push(data[item]);
        }
      }
    
      return temp;
    }
    
    var data = {
      0: "A",
      1: "B",
      2: "C",
    };
    
    renderData(arrayAdapter(data)); // A B C
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    七、装饰器模式

    什么是装饰器模式

    以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。

    是一种“即用即付”的方式,能够在不改变对 象自身的基础上,在程序运行期间给对象动态地 添加职责

    核心思想
    是为对象动态加入行为,经过多重包装,可以形成一条装饰链

    具体实现
    最简单的装饰者,就是重写对象的属性

    var A = {
      score: 10,
    };
    A.score = "分数:" + A.score;
    // 也可以使用传统面向对象的方法来实现装饰,添加技能:
    function Person() {}
    
    Person.prototype.skill = function () {
      console.log("数学");
    };
    
    // 装饰器,还会音乐
    function MusicDecorator(person) {
      this.person = person;
    }
    
    MusicDecorator.prototype.skill = function () {
      this.person.skill();
      console.log("音乐");
    };
    
    // 装饰器,还会跑步
    function RunDecorator(person) {
      this.person = person;
    }
    
    RunDecorator.prototype.skill = function () {
      this.person.skill();
      console.log("跑步");
    };
    
    var person = new Person();
    
    // 装饰一下
    var person1 = new MusicDecorator(person);
    person1 = new RunDecorator(person1);
    
    person.skill(); // 数学
    person1.skill(); // 数学 音乐 跑步
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    也可 我们也可以使用更通用的装饰函数:

    // 装饰器,在当前函数执行前先执行另一个函数
    function decoratorBefore(fn, beforeFn) {
      return function () {
        var ret = beforeFn.apply(this, arguments);
    
        // 在前一个函数中判断,不需要执行当前函数
        if (ret !== false) {
          fn.apply(this, arguments);
        }
      };
    }
    
    function skill() {
      console.log("数学");
    }
    
    function skillMusic() {
      console.log("音乐");
    }
    
    function skillRun() {
      console.log("跑步");
    }
    
    var skillDecorator = decoratorBefore(skill, skillMusic);
    skillDecorator = decoratorBefore(skillDecorator, skillRun);
    
    skillDecorator(); // 跑步 音乐 数学
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    八、代理模式

    什么是代理模式 ?

    当客户不方便直接访问一个 对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。
    替身对象对请求做出一些处理之后,再把请求转交给本体对象代理和本体的接口具有一致性,本体定义了关键功能,而代理是提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情。

    核心思想
    为一个对象提供一个代用品或占位符,以便控制对它的访问

    具体实现
    代理模式主要有三种:保护代理、虚拟代理、缓存代理

    保护代理主要实现了访问主体的限制行为,以过滤字符作为简单的例子:

    // 主体,发送消息
    function sendMsg(msg) {
      console.log(msg);
    }
    // 代理,对消息进行过滤
    function proxySendMsg(msg) {
      // 无消息则直接返回
      if (typeof msg === "undefined") {
        console.log("deny");
        return;
      }
      // 有消息则进行过滤
      msg = ("" + msg).replace(/泥\s*煤/g, "");
    
      sendMsg(msg);
    }
    sendMsg("泥煤呀泥 煤呀"); // 泥煤呀泥 煤呀
    proxySendMsg("泥煤呀泥 煤"); // 呀
    proxySendMsg(); // deny
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    它的意图很明显,在访问主体之前进行控制,没有消息的时候直接在代理中返回了,拒绝访问主体,这属于护代理的形式。有消息的时候对敏感字符进行了处理,这属于虚拟代理的模式。

    虚拟代理在控制对主体的访问时,加入了一些额外的操作,如在滚动事件触发的时候,也许不需要频繁触发,我们可以引入函数节流,这是一种虚拟代理的实现:

    // 函数防抖,频繁操作中不处理,直到操作完成之后(再过 delay 的时间)才一次性处理
    function debounce(fn, delay) {
      delay = delay || 200;
    
      var timer = null;
    
      return function () {
        var arg = arguments;
    
        // 每次操作时,清除上次的定时器
        clearTimeout(timer);
        timer = null;
    
        // 定义新的定时器,一段时间后进行操作
        timer = setTimeout(function () {
          fn.apply(this, arg);
        }, delay);
      };
    }
    
    var count = 0;
    
    // 主体
    function scrollHandle(e) {
      console.log(e.type, ++count); // scroll
    }
    
    // 代理
    var proxyScrollHandle = (function () {
      return debounce(scrollHandle, 500);
    })();
    
    window.onscroll = proxyScrollHandle;
    // 缓存代理可以为一些开销大的运算结果提供暂时的缓存,提升效率。来个栗子——缓存加法操作:
    // 主体
    function add() {
      var arg = [].slice.call(arguments);
    
      return arg.reduce(function (a, b) {
        return a + b;
      });
    }
    
    // 代理
    var proxyAdd = (function () {
      var cache = [];
    
      return function () {
        var arg = [].slice.call(arguments).join(",");
    
        // 如果有,则直接从缓存返回
        if (cache[arg]) {
          return cache[arg];
        } else {
          var ret = add.apply(this, arguments);
          return ret;
        }
      };
    })();
    
    console.log(
      add(1, 2, 3, 4),
      add(1, 2, 3, 4),
    
      proxyAdd(10, 20, 30, 40),
      proxyAdd(10, 20, 30, 40)
    ); // 10 10 100 100
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    九、外观模式

    什么是外观模式

    为子系统中的一组接口提供一个一致的界面,定义一个高层接口,这个接口使子系统更加容易使用

    核心思想
    可以通过请求外观接口来达到访问子系统,也可以选择越过外观来直接访问子系统

    具体实现
    外观模式在 JS 中,可以认为是一组函数的集合

    
    
    // 三个处理函数
    function start() {
      console.log("start");
    }
    
    function doing() {
      console.log("doing");
    }
    
    function end() {
      console.log("end");
    }
    
    // 外观函数,将一些处理统一起来,方便调用
    function execute() {
      start();
      doing();
      end();
    }
    
    // 调用init开始执行
    function init() {
      // 此处直接调用了高层函数,也可以选择越过它直接调用相关的函数
      execute();
    }
    
    init(); // start doing end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    十、迭代器模式

    什么是迭代器模式

    迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
    迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。

    核心思想
    在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素

    具体实现
    JS 中数组的 map forEach 已经内置了迭代器

    [1, 2, 3].forEach(function (item, index, arr) {
      console.log(item, index, arr);
    });
    
    • 1
    • 2
    • 3

    不过对于对象的遍历,往往不能与数组一样使用同一的遍历代码,我们可以封装一下:

    function each(obj, cb) {
      var value;
    
      if (Array.isArray(obj)) {
        for (var i = 0; i < obj.length; ++i) {
          value = cb.call(obj[i], i, obj[i]);
    
          if (value === false) {
            break;
          }
        }
      } else {
        for (var i in obj) {
          value = cb.call(obj[i], i, obj[i]);
    
          if (value === false) {
            break;
          }
        }
      }
    }
    
    each([1, 2, 3], function (index, value) {
      console.log(index, value);
    });
    
    each({ a: 1, b: 2 }, function (index, value) {
      console.log(index, value);
    });
    
    // 0 1
    // 1 2
    // 2 3
    // a 1
    // b 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    再来看一个例子——强行地使用迭代器,来了解一下迭代器也可以替换频繁的条件语句,虽然例子不太好,但在其他负责的分支判断情况下,也是值得考虑的:

    function getManager() {
      var year = new Date().getFullYear();
      if (year <= 2000) {
        console.log("A");
      } else if (year >= 2100) {
        console.log("C");
      } else {
        console.log("B");
      }
    }
    getManager(); // B
    将每个条件语句拆分出逻辑函数,放入迭代器中迭代
    
    function year2000() {
      var year = new Date().getFullYear();
    
      if (year <= 2000) {
        console.log("A");
      }
    
      return false;
    }
    function year2100() {
      var year = new Date().getFullYear();
    
      if (year >= 2100) {
        console.log("C");
      }
    
      return false;
    }
    function year() {
      var year = new Date().getFullYear();
    
      if (year > 2000 && year < 2100) {
        console.log("B");
      }
      return false;
    }
    function iteratorYear() {
      for (var i = 0; i < arguments.length; ++i) {
        var ret = arguments[i]();
    
        if (ret !== false) {
          return ret;
        }
      }
    }
    var manager = iteratorYear(year2000, year2100, year); // B
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
  • 相关阅读:
    计算机毕业设计SSMjspm学科竞赛管理系统【附源码数据库】
    自学前端开发 - VUE 框架 (二):渲染、事件处理、表单输入绑定、生命周期、侦听器、组件基础
    每周一篇论文-规划算法Jump Point Search-Online Graph Pruning for Pathfinding on Grid Maps
    搜索。。。
    ES6新特性
    ENVI: 如何创建GLT文件并基于GLT对图像进行几何校正?
    【红队】ATT&CK - 自启动 - 注册表运行键、启动文件夹
    【数据结构与算法】常见排序算法(Sorting Algorithm)
    看完这篇 教你玩转渗透测试靶机vulnhub——VICTIM: 1
    可以识别到U盘,无法挂载,dev/目录下只有sda,没有sda1问题
  • 原文地址:https://blog.csdn.net/weixin_42662753/article/details/126330409