前言
在面试高级前端时,往往会遇到一些关于设计模式的问题,每次都回答不太理想。恰逢8月更文挑战的活动,准备用一个月时间好好理一下关于设计模式方面的知识点,给自己增加点面试的底气。
在学习设计模式之前,首先要认识到设计模式是个编程思想,对任何编程语言都适用。其次要从设计模式的原则开始学习,故本文将详细介绍设计模式的原则之一开放封闭原则。
官方定义
开放-封闭原则是说软件实体(类、模块、函数等等)应该可以扩展,但是不可修改。
自己的理解
在开发任何系统时,不要指望系统一开始时需求确定,就再也不会发生变化,这是不切实际的想法,既然需求是一定会发生变化的,那么如何设计才能面对需求的改变,不至于修改大部分的代码导致系统不稳定。
既然修改会导致系统不稳定,那么就少修改,不改变代码的主题,使用扩展的方式添加新需求的代码。这就是开放-封闭,是对扩展是开放的,对修改是封闭的。
举一个职场中很常见的现象来解释开放-封闭原则。弹性打卡制度,大家都很熟悉吧,这个制度就是一个很好的开放-封闭原则的应用。
在一家小公司,8点上班,但是有几个骨干员工经常迟到,老板看眼里,心里想这种现象非常不好,于是叫来人事主管提起这个现象,说往后迟到要扣钱,人事主管听了跟老板建议到:“我从考勤记录得知那几个骨干晚上都加班比较晚,加上我们又没有给加班费,这么做,难免会让人心生不满导致人员流失,建议改成弹性上班,比如早上8点到10点弹性,晚上6点到8点弹性下班”。老板听了,想到还是一天工作8个小时没变,于是说到:“先按这样执行一段时间,看看效果”。
在以上的案例中,老板说往后迟到要扣钱,就是修改原本的考勤逻辑代码,可能会导致员工(系统)离职(不稳定)。而人事主管改成弹性上班的建议,只是原本的考勤逻辑代码中扩展出一种计算考勤的方法,其考勤时间还是8个小时不变的,不会导致员工(系统)离职(不稳定)。
实现
1、动态装饰函数的方式
给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(() =>{
//新增逻辑代码
})
2、利用多态的思想
多态的含义:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。或者换句话说,给不同的对象发送同一个消息的时候,这些对象会根据这个消息分别给出不同的 反馈。
多态的思想是将“做什么”和“谁去做以及怎样去做”分离开来,也就是将“不变的事物”与“可能改变的事物”分离开来。正好符合开放-封闭原则。
下面先提供一段不符合开放-封闭原则的代码,再利用多态的思想来将其改造成符合开放-封闭原则。
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());
3、利用回调函数
函数可以作为参数传递给另外一个函数,把这个函数称为回调函数。
可以把一部分易于变化的逻辑封装在回调函数中,然后把回调函数当作参数传入一个稳定和封闭的函数中,该函数是封闭的,不能轻易修改的。
当一个函数要扩展时,可以把扩展的逻辑写入回调函数,当回调函数被执行时,就相当函数被扩展了,从而实现了开放-封闭原则。
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 );
});
4、利用钩子函数
在函数中容易发生变化的地方放置钩子函数,当函数执行到该地方时就会触发钩子函数,钩子函数中写入扩展的逻辑,就相当函数被扩展了,从而实现了开放-封闭原则。
难点
遵循开放-封闭原则的开发过程中,最难的是要找到将要发生变化的地方。将变化的封装起来,可以把系统中稳定不变的部分和容易变化的部分隔离开来。在系统的演变过程中,我们只需要替换那些容易变化的部分,如果这些部分是已经被封装好的,那么替换起来也相对容易。而变化部分之外的就是稳定的部分。在系统的演变过程中,稳定的部分是不需要改变的。
开发时一开始就尽量遵守开放-封闭原则,并不是一件很容易的事情。
一方面,我们需要尽快知道程序在哪些地方会发生变化,这要求我们有一些未卜先知的能力。
另一方面,留给程序员的需求排期并不是无限的,所以我们可以说服自己去接受不合理的代码带来的坑。
在最初开发的时候,先假设变化永远不会发生,这有利于我们迅速完成需求。当变化发生并 且对我们接下来的工作造成影响的时候,可以再回过头来封装这些变化的地方。然后确保我们不 会掉进同一个坑里。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!