JavaScript 设计模式的七大原则(未完成)

Posted My-T

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript 设计模式的七大原则(未完成)相关的知识,希望对你有一定的参考价值。

设计模式(面向对象)有七大设计原则,分别是:

  • 开闭原则:对扩展开放,对修改关闭

  • 单一职责原则:每一个类应该专注于做一件事情

  • 里氏替换原则:父类存在的地方,子类是可以替换的

  • 依赖倒转原则:实现尽量依赖抽象,不依赖具体实现

  • 接口隔离原则

  • 合成服用原则

  • 迪米特法原则

 

一、开闭原则

  开闭原则是面向对象设计中最基础的设计原则。

  对扩展开放:这意味着模块的行为是可以扩展的。当应用的需求改变时,可以对模块进行扩展,使其具有新的功能满足需求的变化。

  对修改关闭:不允许对实体做任何修改,就是这些需要执行多样行为的实体应该设计成不需要修改就可以实现各种的变化,坚持开闭原则有利于用最少的代码进行项目维护。

 

  举个栗子

  需求:商品列表中,如果是男装类型,商品背景色使用蓝色 ,点击之后弹出男装价格;如果是女装,商品背景色使用红色,点击后弹出女装品牌。

  以下为代码示范:

  普通代码我们会这么做:

      // 渲染html的函数中
    if (commodity.type === \'男装\') {
      commodity.css(background, blue);
    } else {
      commodity.css(background, red);
    }

      // 点击事件的函数中
    if (commodity.type === \'男装\') {
      // 弹出价格
      alert(commodity.price);
    } else {
      // 弹出品牌
      alert(commodity.brand);
    }

  看起来一切都很好,代码上线了。过了一阵,PM告知添加一种商品类型,童装,商品背景色使用黄色,点击之后弹出童装的销量。

  那么,代码会被改成下面这样:

    // 渲染html的函数中
    if (commodity.type === \'男装\') {
      commodity.css(background, blue);
    } else if (commodity.type === \'女装\') { // 修改点1 增加女装类型判断
       commodity.css(background, red);
    } else { // 修改点2 增加童装html渲染处理
       commodity.css(background, yellow);
    }

    // 点击事件的函数中
    if (commodity.type === \'男装\') {
        // 弹出价格
      alert(commodity.price);
    } else if (commodity.type === \'女装\') { // 修改点3 增加女装类型判断
        // 弹出价格
      alert(commodity.brand);
    } else { // 修改点4 增加童装点击处理
        // 弹出销量
      alert(commodity.sales);
    }

  ok,以上是可能的代码编写方式,当需求发生变化时,会对原有代码很多地方进行修改,因为修改的地方过于琐碎,修改后。心里一定会慌慌的,同时修改过程也好火眼金睛,生怕漏掉某一处代码,代码可想而知。

  下面我们看一下符合开闭原则的代码:

// getManager的实现
    function getManager(commodity) {
      if (commodity.type === \'男装\') return MaleManager;
      if (commodity.type === \'女装\') return FemaleManager;
    }

    let MaleManager = {
      Settingbackground: function () {
        commodity.css(background, blue);
      },
      Prompt: function () {
        // 弹出价格
        alert(commodity.price);
      }
    };

    let FemaleManager = {
      Settingbackground: function () {
        commodity.css(background, red);
      },
      Prompt: function () {
        // 弹出品牌
        alert(commodity.brand);
      }
    };

  ok,代码量好像多了,多了好几个对象和方法。。。我们接着往下看,当需求发生了变化,童装出现的时候,代码如下:

// getManager的实现
    function getManager(commodity) {
      if (commodity.type === \'男装\') return MaleManager;
      if (commodity.type === \'女装\') return FemaleManager;
      if (commodity.type === \'童装\') return ChildManager; // 修改点1 添加童装管理器的路由,此处可以利用约定的方式而不用修改,后面再讲
    }

    let MaleManager = {
      Settingbackground: function () {
        commodity.css(background, blue);
      },
      Prompt: function () {
        // 弹出价格
        alert(commodity.price);
      }
    };

    let FemaleManager = {
      Settingbackground: function () {
        commodity.css(background, red);
      },
      Prompt: function () {
        // 弹出品牌
        alert(commodity.brand);
      }
    };

    // 修改点2 添加童装管理器,此处其实不算修改,是新增一个对象
    let ChildManager = {
      Settingbackground: function () {
        commodity.css(background, yellow);
      },
      Prompt: function () {
        // 弹出销量
        alert(commodity.sales);
      }
    };

 

  我们可以看到,按照开闭原则设计后的代码,修改点只有一处,修改的地方也可以预判,修改路由方法getManager即可(此处修改其实可以避免);然后新增一个童装manager即可。

二、单一职责原则

  就一个类而言,应该仅有一个引起它变化的原因。

  我们在做编程的时候,很自然的会在一个类上加上各种各样的功能。这意味着,无论任何需求要来,只需要修改这个类,这样其实是糟糕的,维护麻烦,复用不可能。单一职责就是每个类型功能要求单一,一个类只负责干一件事,类的可读性提高,复杂度降低;可读性提高了,代码就更容易维护;变更(需求是肯定会变的,程序员都知道)引起的风险(包括测试的难度,以及需要测试的范围)降低。

   举一个最普通的例子,比如我们写一个最简单的静态页,我们就会创建一个js文件,一个css文件,一个images文件,我们会把对应的文件类型放到对应的文件夹下面。如果我们需要修改样式,我们就会去css文件夹下面去找需要修改样式的对应css文件,比如我们想添加一张图片我们就会去images文件下去放一张需要添加的图片。这样下来我们进行代码修改或添加的时候也会比较方便,节省开发时间。

 

  

 

  代码:

<input type="text" id="ipt">
  <script>
    
    var ipt = document.getElementById(\'ipt\')
    ipt.onclick = function () {
      console.log(\'这是鼠标点击事件!\')
    }
    ipt.ondblclick = function () {
      console.log(\'这是鼠标双击事件!\')
    }
    ipt.onmouseover = function () {
      console.log(\'这是鼠标移入事件!\')
    }
    ipt.onmouseout = function () {
      console.log(\'这是鼠标移出事件!\')
    }
    ipt.onblur = function () {
      console.log(\'这是失去焦点事件!\')
    }
    ipt.keydown = function () {
      console.log(\'这是键盘按下事件!\')
    }
    ipt.keyup = function () {
      console.log(\'这是键盘抬起事件!\')
    }
    
  </script>

  

  比如上面代码,每个事件只执行每个事件对应的代码。

 

 那么单一职责原则的意义何在呢?

  1. 降低类的复杂性,实现什么样的职责都有清晰的定义
  2. 提高可读性
  3. 提高可维护性
  4. 降低变更引起的风险,对系统扩展性和维护性很有帮助

 

三、里氏替换原则

  通俗的定义:所有引用基类的地方必须能透明地使用其子类的对象。

       更通俗的定义:子类可以扩展父类的功能,但不能改变父类原有的功能。

里氏替换原则是继承复用的基石,只有当衍生类可以替换基类,而且功能不受影响时,基类才能真正被复用,而衍生类也能在子类的基础上增加新的功能。(也就是说,任何一个子类的实例都可以替换父类的实例,而功能不受影响)

要遵循里氏替换原则, 需要保证子类在实现父类方法时,必须遵循父类的方法,而且不能重写父类已经定义的方法,可以这样理解继承中父类的关系:

 

  父类中定义的方法,实际上是在设定一系列的规则和契约,虽然它不强制子类必须遵循这些契约,但是如果子类对父类的方法修改,就会对整个继承体系产生破坏。

 

子类必须完全实现父类的方法

里氏替换原则定义了什么是父子,还有一点要注意的,就是儿子不能在父亲会的技能上搞“创新”。
比如父亲会做红烧排骨,儿子在新东方烹饪学校中学到了一招,在红烧排骨里面加糖和醋,变成红烧糖醋排骨,更加美味,看代码,儿子在父亲的基础红烧排骨上加了糖醋,好像没啥问题。

    class Father1 {
      braisedRibs() {
        console.log("红烧排骨");
      }

    }
    class Son1 extends Father1 {
      braisedRibs() {
        console.log("红烧糖醋排骨");
      }
    }

运行下面代码,会打印:红烧排骨。

    var Father = new Father1();
    Father.braisedRibs()

在使用父亲的地方,都能够替换成儿子,并且效果是一样的,那接下来我们改一下代码。

    var BigSon = new Son1();
    Son.braisedRibs()

结果是啥?打印出:红烧糖醋排骨。 父亲会的东西儿子必须继承下来,在父亲不在的时候儿子必须能顶上,但是儿子不能忘掉继承下来的东西,也不能把继承下来的东西修改成别的,这就不遵循里氏替换。

子类可以有自己的个性

我们来看一下父亲的小儿子,小儿子也在新东方烹饪学校学了个手艺。

    class Son2 extends Father1 {
      braisedSweetAndSourPorkRibs() {
        console.log("红烧甜汤排骨");
      }
    }

测试一下是不是好儿子

    var SmallSon = new Son2();
    SmallSonn.braisedRibs()
    SmallSonn.braisedSweetAndSourPorkRibs()

打印出:

红烧排骨
红烧糖醋排骨

这才是 父亲 的好儿子嘛,不仅会红烧排骨,还会红烧糖醋排骨。所以说里氏替换原则就是在定义父子关系,大家都遵守这个定义,就会一代比一代好,不遵守大家也看到了,把前辈传下来的都毁于一旦了。

 

 

四、依赖倒转原则

  定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象

  抽象:即抽象类或接口,两者是不能够实例化的

  细节:即具体的实现类,实现接口或者继承抽象类所产生的类,两者可以通过关键字new直接被实例化

以上是关于JavaScript 设计模式的七大原则(未完成)的主要内容,如果未能解决你的问题,请参考以下文章

面向对象七大设计原则

设计模式 - 七大设计原则

设计模式----七大原则和UML类图

设计模式的七大设计原则

设计模式七大原则

面向对象七大设计原则