概述
《设计模式》这本书完全是从面向对象设计的角度出发的,通过对封装、继承、多态、组合等技术的反复使用,提炼出一些可重复使用的面向对象设计技巧.
所以有一种说法是设计模式仅仅是就面向对象的语言而言的, 其实际上是解决某些问题的一种思想.
❗❗❗❗❗❗ 所有设计模式的实现都遵循一条原则, 即"找出程序中变化的地方, 并将变化封装起来".
相关概念
多态
多态的实际含义是:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。 换句话说,给不同的对象发送同一个消息的时候,这些对象会根据这个消息分别给出不同的反馈.
多态在设计模式中是重中之重,绝大部分设计模式的实现都离不开多态性的思想.
多态背后的思想是将"做什么"和"谁去做以及怎样去做"分离开来,也就是将"不变的事物"与 "可能改变的事物"分离开来.
举例: 分别调用一个猫对象和一个狗对象的叫方法, 猫则是喵~, 狗则旺~, 这就是多态
js
// 动物例子
class Cat {
call() {
console.log('喵喵喵');
}
}
class Dog {
call() {
console.log('汪汪汪');
}
}
const makeSound = (animal) => animal.call()
makeSound(new Cat())
makeSound(new Dog())
// 地图例子
const renderMap = function (map) {
if (map.show instanceof Function) {
map.show();
}
};
const googleMap = {
show: function () {
console.log('开始渲染谷歌地图');
},
};
const baiduMap = {
show: function () {
console.log('开始渲染百度地图');
},
};
const sosoMap = {
show: function () {
console.log('开始渲染搜搜地图');
},
};
renderMap(googleMap); // 输出:开始渲染谷歌地图
renderMap(baiduMap); // 输出:开始渲染百度地图
renderMap(sosoMap); // 输出:开始渲染搜搜地图
Martin Fowler 在《重构:改善既有代码的设计》: 多态的最根本好处在于,你不必再向对象询问"你是什么类型"而后根据得到的答案调用对象的某个行为------你只管调用该行为就是了,其他的一切多态机制都会为你安排妥当。
再来个形象的场景例子:
在电影的拍摄现场,当导演喊出"action"时,主角开始背台词,照明师负责打灯光,后面的群众演员假装中枪倒地,道具师往镜头里撒上雪花。在得到同一个消息时,每个对象都知道自己应该做什么。
如果不利用对象的多态性,而是用面向过程的方式来编写这一段代码,那么相当于在电影开始拍摄之后,导演每次都要走到每个人的面前,确认它们的职业分工(类型),然后告诉他们要做什么。如果映射到程序中,那么程序中将充斥着条件分支语句。
封装
封装的目的是将信息隐藏, 包括封装数据, 封装实现, 封装变化.
AOP
AOP(面向切面编程)是把跟 核心业务逻辑无关且频繁使用的公共功能或操作抽离出来 ,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过"动态织入"的方式掺入业务逻辑模块中。
优点:
- 将分散在不同地方的通用功能集中管理, 方便复用
- 不影响原有业务逻辑代码, 保持业务逻辑模块的纯净和高内聚性
函数柯里化
currying 又称部分求值。一个 currying 的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
简言之, 就是保存参数, 最后统一使用.
js
var currying = function (fn) {
var args = [];
return function () {
if (arguments.length === 0) {
return fn.apply(this, args);
} else {
[].push.apply(args, arguments);
return arguments.callee;
}
};
};
var cost = function () {
var money = 0;
for (var i = 0, l = arguments.length; i < l; i++) {
money += arguments[i];
}
return money;
};
var cost = currying(cost); // 转化成 currying 函数
cost(100); // 未真正求值
cost(200); // 未真正求值
cost(300); // 未真正求值
alert(cost()); // 求值并输出:600
单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点. 单例模式也适用于防止某些操作被重复执行.
重点: 有且仅有一个全局可访问的实例
js
function Person(name) {
this.name = name;
}
Person.prototype.getName = function () {
return this.name;
};
// 使用闭包, 内部维护私有instance变量且不污染全局作用域
Person.getInstance = (function () {
let instance = null;
return function (name) {
if (!instance) {
instance = new Person(name);
}
return instance;
};
})();
var a = Person.getInstance('sven1');
var b = Person.getInstance('sven2');
alert(a === b);
// 进一步简化封装, fn相当于初始化函数, 执行后提供初始单例对象
var getSingle = function (fn) {
var result;
return function () {
return result || (result = fn.apply(this, arguments));
};
};
Person.getInstance = getSingle(() => new Person('sven3'));
单例模式是一种简单但非常实用的模式,特别是惰性单例技术,在合适的时候才创建对象,并且只创建唯一的一个。更奇妙的是,创建对象和管理单例的职责被分布在两个不同的方法中,这两个方法组合起来才具有单例模式的威力
原型模式
原型模式是用于创建对象的一种模式, 一般而言有两种方法:
- 通过对象的构造函数创建
- 未知对象类型时, 可通过克隆复制一个一模一样的对象
js
function Person(name, age) {
this.name = name;
this.age = age;
}
// 构造函数创建对象
const person = new Person('John', 30);
// 克隆创建对象
const copy1 = Object.create(person);
const copy2 = new Object();
Object.setPrototypeOf(copy2, person); // 等价于 create2.__proto__ = person;
策略模式
策略模式定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
"条条大路通罗马", 要实现某一个功能有多种方案可以选择。比如一个压缩文件的程序,既可以选择 zip 算法,也可以选择 gzip 算法。这些算法灵活多样,而且可以随意互相替换。这种解决方案就是本章将要介绍的策略模式。
一个基于策略模式的程序至少由两部分组成:
- 第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。
- 第二个部分是环境类 Context,Context 接受客户的请求,随后把请求委托给某一个策略类。
js
// 策略类, 封装了绩效的具体计算方法
class PerformanceS {
calculate(salary) {
return salary * 4;
}
}
class PerformanceA {
calculate(salary) {
return salary * 3;
}
}
class PerformanceB {
calculate(salary) {
return salary * 2;
}
}
// 环境类, 接收请求的原始数据与策略对象, 并将原始数据委托给策略对象去执行
class Bonus {
constructor() {
this.salary = null;
this.strategy = null;
}
setSalary(salary) {
this.salary = salary;
}
setStrategy(strategy) {
this.strategy = strategy;
}
getBonus() {
return this.strategy.calculate(this.salary);
}
}
// 使用示例:
// const bonus = new Bonus();
// bonus.setSalary(10000);
// bonus.setStrategy(new PerformanceS());
// console.log(bonus.getBonus());
再如: 表单检验, 不同的校验规则可以封装到一个策略类Strategy中, 再通过一个校验类Validator去管理多个不同校验规则以及触发校验, 最后返回校验结果.
js
/***********************策略对象**************************/
const strategies = {
isNonEmpty(value, errorMsg) {
if (value === '') {
return errorMsg;
}
},
minLength(value, length, errorMsg) {
if (value.length < Number(length)) {
return errorMsg;
}
},
isMobile(value, errorMsg) {
if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
return errorMsg;
}
},
};
/***********************Validator 类**************************/
class Validator {
constructor() {
this.cache = []; // 保存校验规则
}
add(dom, rule, errorMsg) {
const parts = rule.split(':'); // 把 strategy 和参数分开
const strategy = parts.shift(); // 用户挑选的 strategy
this.cache.push(() => {
// 把校验的步骤用函数包装起来,并且放入 cache
const args = [dom.value, ...parts, errorMsg]; // 参数列表:value + 规则参数 + 错误信息
return strategies[strategy].apply(dom, args);
});
}
start() {
for (const validatorFunc of this.cache) {
const msg = validatorFunc(); // 开始校验,并取得校验后的返回信息
if (msg) {
// 如果有确切的返回值,说明校验没有通过
return msg;
}
}
return null;
}
}
/***********************使用示例**************************/
const registerForm = document.getElementById('registerForm');
const validateFunc = () => {
const validator = new Validator(); // 创建一个 validator 对象
/***************添加一些校验规则****************/
validator.add(registerForm.userName, 'isNonEmpty', '用户名不能为空');
validator.add(registerForm.password, 'minLength:6', '密码长度不能少于 6 位');
validator.add(registerForm.phoneNumber, 'isMobile', '手机号码格式不正确');
const errorMsg = validator.start(); // 获得校验结果
return errorMsg; // 返回校验结果
};
registerForm.onsubmit = function () {
const errorMsg = validateFunc(); // 如果 errorMsg 有确切的返回值,说明未通过校验
if (errorMsg) {
alert(errorMsg);
return false; // 阻止表单提交
}
};
代理模式
代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问 ,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。 本质上代理就是一个中间协助的"助理"角色, 协助做一些事情.
基本示例:
- 经纪人是明星的代理, 经纪人会把商业演出的细节和报酬都谈好之后,再把合同交给明星签.
- 香港带货, 到香港进货的人是客户的代理
js
var myImage = (function () {
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc: function (src) {
imgNode.src = src;
},
};
})();
var proxyImage = (function () {
var img = new Image();
img.onload = function () {
myImage.setSrc(this.src);
};
return {
setSrc: function (src) {
myImage.setSrc('file:// /C:/Users/svenzeng/Desktop/loading.gif');
img.src = src;
},
};
})();
proxyImage.setSrc('http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg');
代理和本体接口的一致性, 如上例中的本体和代理都对外提供了 setSrc 方法
- 在用户看来, 代理和本体是一致的, 用户只关心能否得到想要的结果
- 任何使用本体的地方都可以替换成使用代理
接口的一致性使得本体和代理可以随时灵活互相替换和切换.
一般会以代理实际做的事情来命名代理, 如: 缓存代理, 顾名思义该代理是专门用于为本体做一些缓存的.
缓存代理:
js
var mult = function () {
console.log('开始计算乘积');
var a = 1;
for (var i = 0, l = arguments.length; i < l; i++) {
a = a * arguments[i];
}
return a;
};
mult(2, 3); // 输出:6
mult(2, 3, 4); // 输出:24
var proxyMult = (function(){
var cache = {};
return function(){
var args = Array.prototype.join.call( arguments, ',' );
if ( args in cache ){
return cache[ args ];
}
return cache[ args ] = mult.apply( this, arguments );
}
})();
proxyMult( 1, 2, 3, 4 ); // 输出:24
proxyMult( 1, 2, 3, 4 ); // 输出:24
好处显而易见, 职责单一明确且高内聚低耦合, mult只关注乘积计算的实现即可, 代理只负责为mult本体添加缓存. 假设需要修改 mult 的实现方法, 不会影响缓存代理; 同理, 假设后续需要改变缓存策略或者删除缓存, 直接将代理替换回本体的 mult 即可, 非常方便灵活.
迭代器模式
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。
发布-订阅模式
即观察者模式, 简言之, 预先订阅一个函数或事件, 在某个特定的异步时刻这个函数或事件将会被触发.
最典型的例子: 你关注(订阅)某个主播, 当主播发布新作品时(状态发生改变), 所有关注了这个主播的粉丝都会收到新作品内容的通知(发布通知).
通用实现:
js
const event = (function () {
const clientList = [];
function listen(key, fn) {
if (!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn);
}
function trigger() {
let key = Array.prototype.shift.call(arguments),
fns = this.clientList[key];
if (!fns || fns.length === 0) {
// 如果没有绑定对应的消息
return false;
}
for (let i = 0, fn; (fn = fns[i++]); ) {
fn.apply(this, arguments);
}
}
function remove(key, fn) {
var fns = this.clientList[key];
if (!fns) {
return false;
}
if (!fn) {
fns && (fns.length = 0);
} else {
for (var l = fns.length - 1; l >= 0; l--) {
var _fn = fns[l];
if (_fn === fn) {
fns.splice(l, 1);
}
}
}
}
return {
listen,
remove,
trigger,
};
})();
Event.listen('alltoowell', function (...args) {
console.log(args);
});
Event.trigger('alltoowell', 666);
命令模式
命令模式中的命令(command)指的是一个执行某些特定事情的指令。
命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。
即发送者只关注这个指令的触发与执行的结果, 不关心这个指令由谁执行以及背后做了哪些事情.
示例:
拿订餐来说,客人 需要向厨师发送请求,但是完全不知道这些厨师的名字和联系方式,也不知道厨师炒菜的方式和步骤。命令模式把客人订餐的请求封装成 command 对象,也就是订餐中的订单对象。这个对象可以在程序中被四处传递,就像订单可以从服务员手中传到厨师的手中。这样一来,客人不需要知道厨师的名字,从而解开了请求调用者和请求接收者之间的耦合关系。
组合模式
不变之处 在于都是由若干部分组成, 变之处在于组成部分的数量与顺序等是不确定的.
组合模式就是用小的子对象来构建更大的对象,而这些小的子对象本身也许是由更小的"孙对象"构成的。组合模式将对象组合成树形结构,以表示"部分-整体"的层次结构。
基本对象可以被组合成更复杂的组合对象,组合对象又可以被组合,这样不断递归下去,这棵树的结构可以支持任意多的复杂度。
扫描文件夹
js
/******************************* Folder ******************************/
var Folder = function (name) {
this.name = name;
this.files = [];
};
Folder.prototype.add = function (file) {
this.files.push(file);
};
Folder.prototype.scan = function () {
console.log('开始扫描文件夹: ' + this.name);
for (var i = 0, file, files = this.files; (file = files[i++]); ) {
file.scan();
}
};
/******************************* File ******************************/
var File = function( name ){
this.name = name;
};
File.prototype.add = function(){
throw new Error( '文件下面不能再添加文件' );
};
File.prototype.scan = function () {
console.log('开始扫描文件: ' + this.name);
};
var folder = new Folder('学习资料');
var folder1 = new Folder('JavaScript');
var folder2 = new Folder('jQuery');
var file1 = new File('JavaScript 设计模式与开发实践');
var file2 = new File('精通 jQuery');
var file3 = new File('重构与模式');
folder1.add(file1);
folder2.add(file2);
folder.add(folder1);
folder.add(folder2);
folder.add(file3);
模板方法模式
模板方法模式是一种使用继承就可以实现的简单模式。
模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。
享元模式
享元(flyweight)模式是一种用于性能优化 的模式,"fly"在这里是苍蝇的意思,意为蝇量级。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。
通用对象池:
js
var objectPoolFactory = function (createObjFn) {
var objectPool = [];
return {
create: function () {
var obj = objectPool.length === 0 ? createObjFn.apply(this, arguments) : objectPool.shift();
return obj;
},
recover: function (obj) {
objectPool.push(obj);
},
};
};
职责链模式
定义: 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系 ,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
职责链模式的名字非常形象,一系列可能会处理请求的对象被连接成一条链,请求在这些对象之间依次传递,直到遇到一个可以处理它的对象.
示例:
- 如果早高峰能顺利挤上公交车的话,那么估计这一天都会过得很开心。因为公交车上人实在太多了,经常上车后却找不到售票员在哪,所以只好把两块钱硬币往前面递。除非你运气够好,站在你前面的第一个人就是售票员,否则,你的硬币通常要在 N 个人手上传递,才能最终到达售票员的手里。
职责链模式的最大优点:请求发送者只需要知道链中的第一个节点,从而弱化了发送者和一组接收者之间的强联系。如果不使用职责链模式,那么在公交车上,我就得先搞清楚谁是售票员,才能把硬币递给他。
js
function Chain(fn) {
this.fn = fn;
this.nextProcessor = null; // 下一个处理节点
}
Chain.prototype.setNextProcessor = function (nextProcessor) {
return this.nextProcessor = nextProcessor;
};
Chain.prototype.process = function () {
const result = this.fn.apply(this, arguments);
if (result === 'nextProcessor') {
return this.nextProcessor && this.nextProcessor.process.apply(this.nextProcessor, arguments);
}
return result;
};
var order500 = function (orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log('500 元定金预购,得到 100 优惠券');
} else {
return 'nextProcessor'; // 我不负责处理本次请求,把请求往后面传递
}
};
var order200 = function (orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log('200 元定金预购,得到 50 优惠券');
} else {
return 'nextProcessor'; // 我不负责处理本次请求,把请求往后面传递
}
};
var orderNormal = function (orderType, pay, stock) {
if (stock > 0) {
console.log('普通购买,无优惠券');
} else {
console.log('手机库存不足');
}
};
var chainOrder500 = new Chain(order500);
var chainOrder200 = new Chain(order200);
var chainOrderNormal = new Chain(orderNormal);
chainOrder500.setNextProcessor(chainOrder200);
chainOrder200.setNextProcessor(chainOrderNormal);
chainOrder500.process(1, true, 500); // 输出:500 元定金预购,得到 100 优惠券
chainOrder500.process(2, true, 500); // 输出:200 元定金预购,得到 50 优惠券
chainOrder500.process(3, true, 500); // 输出:普通购买,无优惠券
chainOrder500.process(1, false, 0); // 输出:手机库存不足
// 利用js函数式特性, 更简便实现职责链模式;
Function.prototype.after = function (fn) {
const self = this;
return function () {
const ret = self.apply(this, arguments);
if (ret === 'nextProcessor') {
return fn.apply(this, arguments);
}
return ret;
}
}
const order = chainOrder500.after(chainOrder200).after(chainOrderNormal)
order(1, true, 500)
中介者模式
场景描述: 在程序里,也许一个对象会和其他 10 个对象打交道,所以它会保持 10 个对象的引用。当程序的规模增大,对象会越来越多,它们之间的关系也越来越复杂,难免会形成网状的交叉引用(多对多关系)。当我们改变或删除其中一个对象的时候,很可能需要通知所有引用到它的对象.
中介者模式: 解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系.
经典应用场景:
- 单点登录, 用户在多个相互信任的系统中,只需要进行一次身份验证,就能访问所有授权的系统
- 统一接口层
- 团队射击游戏中, 两个队伍的玩家的存活与死亡等信息的通知
- 每个玩家加入队伍或死亡移出队伍等等的信息通知中介者对象, 由中介者对象去通知所有其他玩家, 从而解耦了玩家与玩家之间直接的多对多关系
装饰者模式
在不改变对象自身的基础 上, 动态地给某个对象添加额外的职责,而不会影响从这个类中派生的其他对象.
用AOP装饰函数示例1
js
// 装饰 fn 动态添加记录日志的功能, 以及try-catch错误捕获, 更新loading状态
const wrapperFnWithLog = (fn: Function, config: { action: string } | any = {}) => {
return async (...args: any[]) => {
const { action, hideSuccess = false } = config;
try {
cameraStore.loading = true;
cameraStore.addLog({
content: action,
});
await fn(...args);
!hideSuccess &&
cameraStore.addLog({
type: 'success',
content: `${action}成功`,
});
} catch (error: any) {
cameraStore.addLog({
content: `${action}失败: ${error.message}`,
type: 'error',
});
ElMessage.error(action);
console.error(`${action}失败: `, error);
} finally {
cameraStore.loading = false;
}
};
};
用AOP装饰函数示例2
js
Function.prototype.before = function (beforefn) {
var __self = this; // 保存原函数的引用
return function () {
beforefn.apply(this, arguments); // 执行新函数,且保证 this 不被劫持,新函数接受的参数
// 也会被原封不动地传入原函数,新函数在原函数之前执行
return __self.apply(this, arguments); // 执行原函数并返回原函数的执行结果,
};
};
Function.prototype.after = function (afterfn) {
var __self = this;
return function () {
var ret = __self.apply(this, arguments);
afterfn.apply(this, arguments);
return ret;
};
};
状态模式
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类.
一个对象有多个不同的状态, 每种状态下执行相同的操作会产生不同的行为, 即类似多态的概念. 其实就是一个状态机, 适用于多种状态间切换去执行不同的操作.
场景例子:
- 文件上传过程中存在扫描, 上传中, 暂停, 上传成功或失败几种状态
- 音乐播放器存在加载中, 播放中, 暂停, 播放完毕几种状态
状态模式的通用结构:
- 存在一个上下文对象context, 其将持有每种状态类的实例的引用
- 实现各种状态类, 状态类的实例对象将持有context的引用
- 状态类封装并实现了对应状态下的行为
js
var Light = function () {
this.offLightState = new OffLightState(this); // 持有状态对象的引用
this.weakLightState = new WeakLightState(this);
this.strongLightState = new StrongLightState(this);
this.button = null;
};
Light.prototype.init = function () {
var button = document.createElement('button'),
self = this;
this.button = document.body.appendChild(button);
this.button.innerHTML = '开关';
this.currState = this.offLightState; // 设置默认初始状态
this.button.onclick = function () {
// 定义用户的请求动作
self.currState.buttonWasPressed();
};
};
var OffLightState = function (light) {
this.light = light;
};
OffLightState.prototype.buttonWasPressed = function () {
console.log('弱光');
this.light.setState(this.light.weakLightState);
};
触发当前状态下的操作, 然后再切换到另一个状态.
适配器模式
适配器模式的作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。
场景例子:
- 一拖三数据线, type-a / type-c / usb 等接口互转
- 扩展坞
- 电源适配器
设计原则和编程技巧
每种设计模式都是为了让代码迎合其中一个或多个原则而出现的,它们本身已经融入了设计模式之中,给面向对象编程指明了方向。
前辈总结的这些设计原则通常指的是单一职责原则、里氏替换原则、依赖倒置原则、接口隔离原则、合成复用原则和最少知识原则。
代码优劣的评判标准
- 主功能入口函数是否足够的精简? 过于冗长会导致业务逻辑的脉络模糊, 可读性差
- 从增删改角度出发, 函数是否具有弹性, 增删改时是否需要深入函数内部实现去操作(开闭原则)
- 从复用性角度考虑, 是否便于类似的应用场景的直接复用
- 单一职责原则, 面向对象设计提倡将行为分布到细粒度 的对象之中, 即高内聚, 低耦合
单一职责原则
单一职责原则(SRP)的职责被定义为"引起变化的原因"。如果我们有两个动机去改写一个方法,那么这个方法就具有两个职责。 每个职责都是变化的一个轴线,如果一个方法承担了过多的职责,那么在需求的变迁过程中,需要改写这个方法的可能性就越大。
SRP 原则体现为:一个对象(方法)只做一件事情。
然而并不是所有的职责都需要一一分离, 分离的颗粒度以及是否需要分离取决于这些职责互相的关联性, 强关联或者时常一起改变的则不需要分离; 相对固定不变的或者无需考虑复用性的职责也不需要分离.
最少知识原则
又称迪米特法则, 一个软件实体应当尽可能少地与其他实体发生相互依赖。这里的软件实体是一个广义的概念,不仅包括对象,还包括系统、类、模块、函数、变量等。
开闭原则
在项目需求变迁的过程中,我们经常会找到相关代码,然后改写它们。这个过程很容易因为修改了源码引入了更多的bug.
开闭原则: 当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,但是不允许改动程序的源代码。
js
Function.prototype.after = function (afterfn) {
var __self = this;
return function () {
var ret = __self.apply(this, arguments);
afterfn.apply(this, arguments);
return ret;
};
};
window.onload = (window.onload || function () {}).after(function () {
console.log(document.getElementsByTagName('*').length);
});
其实就是告诫我们不要轻易改动他人的代码, 以防产生某些意料之外的副作用而引入bug.