『面试的底气』—— 设计模式之开放封闭原则|8月更文挑战

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战前端

前言

在面试高级前端时,每每会遇到一些关于设计模式的问题,每次都回答不太理想。恰逢8月更文挑战的活动,准备用一个月时间好好理一下关于设计模式方面的知识点,给本身增长点面试的底气。程序员

在学习设计模式以前,首先要认识到设计模式是个编程思想,对任何编程语言都适用。其次要从设计模式的原则开始学习,故本文将详细介绍设计模式的原则之一开放封闭原则web

官方定义

开放-封闭原则是说软件实体(类、模块、函数等等)应该能够扩展,可是不可修改。面试

本身的理解

在开发任何系统时,不要期望系统一开始时需求肯定,就不再会发生变化,这是不切实际的想法,既然需求是必定会发生变化的,那么如何设计才能面对需求的改变,不至于修改大部分的代码致使系统不稳定。ajax

既然修改会致使系统不稳定,那么就少修改,不改变代码的主题,使用扩展的方式添加新需求的代码。这就是开放-封闭,是对扩展是开放的,对修改是封闭的。编程

举一个职场中很常见的现象来解释开放-封闭原则。弹性打卡制度,你们都很熟悉吧,这个制度就是一个很好的开放-封闭原则的应用。设计模式

在一家小公司,8点上班,可是有几个骨干员工常常迟到,老板看眼里,内心想这种现象很是很差,因而叫来人事主管提起这个现象,说日后迟到要扣钱,人事主管听了跟老板建议到:“我从考勤记录得知那几个骨干晚上都加班比较晚,加上咱们又没有给加班费,这么作,不免会让人心生不满致使人员流失,建议改为弹性上班,好比早上8点到10点弹性,晚上6点到8点弹性下班”。老板听了,想到仍是一天工做8个小时没变,因而说到:“先按这样执行一段时间,看看效果”。markdown

在以上的案例中,老板说日后迟到要扣钱,就是修改本来的考勤逻辑代码,可能会致使员工(系统)离职(不稳定)。而人事主管改为弹性上班的建议,只是本来的考勤逻辑代码中扩展出一种计算考勤的方法,其考勤时间仍是8个小时不变的,不会致使员工(系统)离职(不稳定)。app

实现

一、动态装饰函数的方式

Function的原型链上添加一个extend方法来对函数进行扩展,在extend中利用_this.apply(this , arguments)执行要扩展的函数,并将直接结果result返回。而后执行fun.apply(this , arguments),其中fun就是调用函数extend时传入的要扩展的函数。这样不去修改函数的原有代码,也能往函数中添加新的逻辑,实现了开放-封闭。编程语言

Function.prototype.extend = function(fun){
  var _this = this;
  return function(){
     const result = _this.apply(this , arguments);
     fun.apply(this , arguments);
     return result
  }
}
const a = () =>{
    //旧的逻辑代码
}
const b = a.extend(() =>{
   //新增逻辑代码
})
复制代码

二、利用多态的思想

多态的含义:同一操做做用于不一样的对象上面,能够产生不一样的解释和不一样的执行结果。或者换句话说,给不一样的对象发送同一个消息的时候,这些对象会根据这个消息分别给出不一样的 反馈。

多态的思想是将“作什么”和“谁去作以及怎样去作”分离开来,也就是将“不变的事物”与“可能改变的事物”分离开来。正好符合开放-封闭原则。

下面先提供一段不符合开放-封闭原则的代码,再利用多态的思想来将其改形成符合开放-封闭原则。

class A {
  constructor() {}
  init() {
    console.log('初始化A');
  }
}
class B {
  constructor() {}
  init() {
    console.log('初始化B');
  }
}

const init = (type) => {
  if (type === 'A') {
    new A().init();
  } else if (type === 'B') {
    new B().init();
  }
};

init('A');
init('B');
复制代码

init函数中经过判断传入type来分别初始化A类和B类,倘若又来一个C类,要用init函数来初始化,要怎么办呢?直接修改init函数:

class C {
  constructor() {}
  init() {
    console.log('初始化C');
  }
}
const init = (type) => {
  if (type === 'A') {
    new A().init();
  } else if (type === 'B') {
    new B().init();
  }else if(type === 'C'){
    new C().init();
  }
};
复制代码

如果这样处理,日后每新增一个类要初始化,都要去改动init函数的内部实现,是违背了开放-封闭原则。

利用多态的思想,把程序中不变的部分隔离出来(都会调用类的init方法进行初始化), 而后把可变的部分(类的实例化)封装起来,这样一来程序就具备了可扩展性。故能够这样改造init函数。

const init = (obj) =>{
  if(obj.init instanceof Function){
    obj.init();
  }
}
init(new A());
init(new B());
init(new C());
复制代码

三、利用回调函数

函数能够做为参数传递给另一个函数,把这个函数称为回调函数。

能够把一部分易于变化的逻辑封装在回调函数中,而后把回调函数看成参数传入一个稳定和封闭的函数中,该函数是封闭的,不能轻易修改的。

当一个函数要扩展时,能够把扩展的逻辑写入回调函数,当回调函数被执行时,就至关函数被扩展了,从而实现了开放-封闭原则。

Jq的ajax就是利用了回调函数进行扩展,每次请求回来的数据都用回调函数进行处理。

var getUserInfo = function( callback ){
   $.ajax( 'http:// xxx.com/getUserInfo', callback );
};
getUserInfo( function( data ){
   console.log( data.userName );
});
getUserInfo( function( data ){
   console.log( data.userId );
}); 
复制代码

四、利用钩子函数

在函数中容易发生变化的地方放置钩子函数,当函数执行到该地方时就会触发钩子函数,钩子函数中写入扩展的逻辑,就至关函数被扩展了,从而实现了开放-封闭原则。

难点

遵循开放-封闭原则的开发过程当中,最难的是要找到将要发生变化的地方。将变化的封装起来,能够把系统中稳定不变的部分和容易变化的部分隔离开来。在系统的演变过程当中,咱们只须要替换那些容易变化的部分,若是这些部分是已经被封装好的,那么替换起来也相对容易。而变化部分以外的就是稳定的部分。在系统的演变过程当中,稳定的部分是不须要改变的。

开发时一开始就尽可能遵照开放-封闭原则,并非一件很容易的事情。

一方面,咱们须要尽快知道程序在哪些地方会发生变化,这要求咱们有一些未卜先知的能力。

另外一方面,留给程序员的需求排期并非无限的,因此咱们能够说服本身去接受不合理的代码带来的坑。

在最初开发的时候,先假设变化永远不会发生,这有利于咱们迅速完成需求。当变化发生并 且对咱们接下来的工做形成影响的时候,能够再回过头来封装这些变化的地方。而后确保咱们不 会掉进同一个坑里。

相关文章
相关标签/搜索