JS 简说设计模式(持续追加)

258 阅读5分钟

不论是每次优化代码时还是找工作面试时,都会重新准备一遍设计模式这块的知识点。设计模式我是学了忘了忘了学,每次费劲写了demo,自认为掌握了,结果还是在工作中忘记了或者觉得用起来不称手。时间一长又不记得啥了。所以这次我打算慢慢来,就记一些简单抽象的概念,以及简单的代码实现。 路要一步不走,饭要一口口吃,不急不急。

单例模式

避免重复创建相同的对象实例。在js这种单线程的环境中,单例模式很好实现,如java,则需要避免多线程环境下可能重复创建对象实例的问题。

用途

  • 如有全局操作的需求可考虑单例,创建唯一的全局操作入口,确保系统状态唯一。
  • 避免频繁创建对象引起的高消耗,阻塞。比如移动端读取缓存,Node对系统文件的操作等,都应尽量避免重复的创建对象,避免多余的系统消耗。

实现方式

实现方式:闭包。我发现闭包真是一个好东西,既能保留住数据状态,又能起到私有变量的作用,避免外界的访问。很多库和设计模式都涉及到闭包。第一个想起来的是react-redux,前两个月刚捋了一遍。

 DanliProxyFunc = function (Fn) {
       let instance;
       return function DanliProxy(name) {
         if (!instance) {
           instance = new Fn(name);
         }
         return instance;
       };
     };

  function Cat(name) {
       this.name = name;
     }
     
   Cat.prototype.getName = function () {
       return this.name;
     };
     
    let SingleCat = DanliProxyFunc(Cat);

     let cat1 = new SingleCat("123");
     let cat2 = new SingleCat("234");
     
     console.log(a === b);//true

组合模式

将类似的动作组合在一起,搭积木。几个小模块组合成一个大模块,几个大模块组合成更大的模块。 我最近使用的小米之家全家桶,就是一个组合模式。每个独立的场景,比如开灯、打开小爱同学、打开电视,这些都是独立的动作。但将他们组合到一起就形成了一个特定的场景。比如我回家打开门后,自动触发上述的多组动作。那么回家这个场景就是一个大模块。

用途

  • 可以模块化多个相似的动作,形成可复用的大模块(我习惯叫它场景)
  • 如果一个场景下的动作组合需要变换,则只需增删这个场景下的动作即可,不需要改动使用场景的代码。做到场景和使用场景的分离。
function ZH(){
       return {
           commandList:[],
           add:function(command){
               this.commandList.push(command);
           },
           excude:function(){
               this.commandList.forEach(command=>{
                   command.excude();
               })
           }
       }
   }

   let command1 = {
       excude:()=>console.log('打开门窗')
   }
   let command2 = {
       excude:()=>console.log('打开窗帘') 
   }
   let command3 = {
       excude:()=>console.log('打开台灯') 
   }
   let command4 = {
       excude:()=>console.log('打开水龙头') 
   }
   let command5 = {
       excude:()=>console.log('打开空调') 
   }
   let command6 = {
       excude:()=>console.log('打开电视') 
   }
   let command7 = {
       excude:()=>console.log('打开电扇') 
   }

   let ZH1 = ZH();
   ZH1.add(command1);
   ZH1.add(command2);
   ZH1.add(command3);
   ZH1.add(command4);

   let ZH2 = ZH();
   ZH1.add(command5);
   ZH1.add(command6);
   ZH1.add(command7);

   let ZH_SUPER = ZH();
   ZH_SUPER.add(ZH1);
   ZH_SUPER.add(ZH2);

   ZH_SUPER.excude();

享元模式 Flyweight

享元,共享某一个变量、方法等。减少不必要的资源创建。比如线程池、重复利用回收的资源。

用途

  • 减少内存开支
  • 因为直接使用已经创建的,状态处于空闲的对象,所以性能较好。

实现方式

  function threadFactory(createThreadMethod) {
        let list = [];
        return {
          create: function(name) {
            return list.length === 0 ? createThreadMethod(name) : list.shift();
          },
          recover: function(thread) {
            list.push(thread);
          }
        };
      }

      function Thread(name) {
        this.name = name;
      }
      Thread.prototype.getName = function() {
        return this.name;
      };

      let threadP = threadFactory((name) => {
        return new Thread(name);
      });

      let thread1 = threadP.create('thread1');
      let thread2 = threadP.create('thread2');
      let thread3 = threadP.create('thread3');
      threadP.recover(thread1);//回收线程1
      let thread4 = threadP.create('thread4');//不会创建,直接复用线程1


      //输出:thread1 thread2 thread3 thread1
      console.log(
        thread1.getName(),
        thread2.getName(),
        thread3.getName(),
        thread4.getName()
      );

职责链模式

链,说明是事件发起者和事件消化者是通过一条链去链接的。就像现实中的一条环环相扣的铁链,中间随意拆除铁链中的几个环节,也不会影响发起者和消化者之间的通信,只是影响了中间的处理环节——少了拆除的环节的处理。 需要注意的一点是,这里的每个环(处理函数)的输入和输出的类型如果都保持一致,如huan1(number)=> object,huan2(number)=> object...这样的话,随意拆除则不会影响后面的环的处理。如果不能一致,则需要注意环节之间输入输出类型的转换。 比如promise的then,我们可以连接多个then去处理上一个then输出的对象。这个就是一个职责链的体现。

用途

  • 如上面所说,这种模式,很明显的减少了发起者和消化者之间的逻辑耦合。
  • 处理流程的变更,只需在链子上增删改而已,无需修改发起者和消化者的处理逻辑。
  • 处理流程直观,后期维护很方便。
/**
场景:最近很穷,我去向朋友借钱,每个朋友的接受程度不一样,根据我想借的钱数,
会有不同的朋友借给我。那最简单的逻辑就是写一堆ifelse的逻辑分支。
那如果是用职责链写,则如下所示:
**/
 /**
       * 将不同逻辑分别写成独立的函数
       * 再写一个chain函数去串联起他们
       * 如果中间改变,直接增删改函数,chain的顺序 即可
       * */
      function borrow1000(money) {
        if (money < 1000 && money > 0) {
          console.log("找小李");
        } else {
          return "next";
        }
      }
      function borrow2000(money) {
        if (money < 2000 && money > 1000) {
          console.log("找小孙");
        } else {
          return "next";
        }
      }
      function borrow3000(money) {
        if (money < 3000 && money > 2000) {
          console.log("找小钱");
        } else {
          return "next";
        }
      }
      function borrow4000(money) {
        if (money < 4000 && money > 3000) {
          console.log("找小S");
        } else {
          return "next";
        }
      }

      function borrowNo(money) {
        console.log("别找了 没人借给你");
      }

      function Chain(fn) {
        this.fn = fn;
        this.nextChain = null;
      }

      Chain.prototype.process = function(money) {
        let ret = this.fn(money);
        if (ret === "next") {
          this.nextChain.process(money);
        }
      };

      Chain.prototype.next = function(chain) {
        this.nextChain = chain;
        return chain;
      };

      let c1 = new Chain(borrow1000);
      let c2 = new Chain(borrow2000);
      let c3 = new Chain(borrow3000);
      let c4 = new Chain(borrow4000);
      let no = new Chain(borrowNo);

      c1.next(c2)
        .next(c3)
        .next(c4)
        .next(no);

      c1.process(2500);

这样,我可以随意删除或者增加中间的环(处理函数)。