假设有一个空房间,我们要日复一日地往里面放一些东西。最简单的办法当然是把这些东西直接扔进去,但是时间久了,就会发现很难从这个房子里找到自己想要的东西,要调整某几样东西的位置也不容易。所以在房间里做一些柜子也许是个更好的选择,虽然柜子会增加我们的成 本,但它可以在维护阶段为我们带来好处。使用这些柜子存放东西的规则,就是一种设计模式。
设么是设计模式?找出 程序中变化的地方,并将变化封装起来。这种封装就是设计模式。它的关键是意图,而不是结构。学习设计模式,有助于写出可复用和可维护性高的程序。
1.程序设计的原则
单一职责原则(SRP)
一个对象或方法只做一件事情。如果一个方法承担了过多的职责,那么在需求的变迁过程中,需要改写这个方法的可能性就越大。
也就是说,应该把对象或方法划分成较小的粒度。
最少知识原则(LKP)
一个软件实体应当尽可能少地与其他实体发生相互作用。
应当尽量减少对象之间的交互。如果两个对象之间不必彼此直接通信,那么这两个对象就不要发生直接的相互联系,可以转交给第三方进行处理。
划分模块的一个准则就是高内聚低耦合。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性(内聚性)越差( 降低耦合性,可以提高其独立性)。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。
开放-封闭原则(OCP)
软件实体(类、模块、函数)等应该是可以 扩展的,但是不可修改。
当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,尽量避免改动程序的源代码,防止影响原系统的稳定。
2.常用模式
常用的有:单例模式、工厂模式、装饰模式、发布-订阅模式、观察者模式、中介者模式等。
3.单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点。核心:确保只有一个实例,并提供全局访问。
实现方法:使用闭包缓存一个局部变量,这个局部变量用来缓存仅有的实例。
function SetManager(name) {
this.manager = name;
}
SetManager.prototype.getName = function() {
console.log(this.manager);
};
var SingletonSetManager = (function() {
var manager = null;
return function(name) {
if (!manager) {
manager = new SetManager(name);
}
return manager;
}
})();
SingletonSetManager('a').getName(); // a
SingletonSetManager('b').getName(); // a
SingletonSetManager('c').getName(); // a
4.工厂模式
工厂模式是用来创建对象的一种设计模式。不暴露对象创建的逻辑,而是将逻辑封装在一个函数内,那么这个函数可以成为工厂。
工厂是用构造函数的方法来创建对象。通过使用一个共同的接口来指向新创建的对象。
工厂模式根据抽象程度的不同可以分为:1.简单工厂 2.工厂方法 3.抽象工厂
简单工厂
优点:你只需要传递一个合法的参数,就可以获取到你想要的对象,而无需知道创建的具体的细节。
缺点:但是在函数内包含了所有对象的构造函数和判断逻辑的代码, 每次如果需要添加一个对象,那么我们需要新增一个构造函数,当我们需要维护的对象不是上面这2个,而是20个或者更多,那么这个函数将会成为超级函数,使得我们难以维护。所以简单工厂模式只适用于在创建时对象数量少,以及逻辑简单的情况。
let factory = function (role) {
function superman() {
this.name ='超级管理员',
this.role = ['修改密码', '发布消息', '查看主页']
}
function commonMan() {
this.name = '普通游客',
this.role = ['查看主页']
}
switch(role) {
case 'superman':
return new superman();
break;
case 'man':
return new commonMan();
break;
default:
throw new Error('参数错误')
}
}
let superman = factory('superman');
let man = factory('man');
工厂方法:
工厂方法模式本意是将实际创造的对象推迟到子类中,这样核心类就变成了抽象类。
let factory = function (role) {
if(this instanceof factory) {
var s = new this[role]();
return s;
} else {
return new factory(role);
}
}
factory.prototype = {
admin: function() {
this.name = '平台用户';
this.role = ['登录页', '主页']
},
common: function() {
this.name = '游客';
this.role = ['登录页']
},
test: function() {
this.name = '测试';
this.role = ['登录页', '主页', '测试页'];
this.test = '我还有一个测试属性哦'
}
}
let admin = new factory('admin');
let common = new factory('common');
let test = new factory('test');
如果使用时忘记加new了, 那么我们就获取不到admin,common等对象了,这就比较安全了。
5.装饰模式
定义:以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。
是一种“即用即付”的方式,能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。
核心:是为对象动态加入行为,经过多重包装,可以形成一条装饰链
实现:最简单的装饰者,就是重写对象的属性。
var A = {
score: 10
};
A.score = '分数:' + A.score;
可以使用传统面向对象的方法来实现装饰,添加技能。
function Person() {}
Person.prototype.skill = function() {
console.log('数学');
};
// 装饰器,还会音乐
function MusicDecorator(person) {
this.person = person;
}
MusicDecorator.prototype.skill = function() {
this.person.skill();
console.log('音乐');
};
// 装饰器,还会跑步
function RunDecorator(person) {
this.person = person;
}
RunDecorator.prototype.skill = function() {
this.person.skill();
console.log('跑步');
};
var person = new Person();
// 装饰一下
var person1 = new MusicDecorator(person);
person1 = new RunDecorator(person1);
person.skill(); // 数学
person1.skill(); // 数学 音乐 跑步
6.发布-订阅模式
定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
一个对象不用再显式地调用另外一个对象的某个接口而实现通知。
与传统的发布-订阅模式实现方式(将订阅者自身当成引用传入发布者)不同,在JS中通常使用注册回调函数的形式来订阅。
发布订阅是一种消息范式,消息的发送者(发布者)不会直接将消息传给特定的接收者(订阅者),而是将发布的消息分为不同的类别,无需了解有哪些订阅者。同样,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者存在,无需了解发布者发出了哪些消息。
优势:时间上的解耦,对象之间解耦。可以用在异步编程中与MVVM框架中。
缺点:1.创建订阅者本身要消耗一定的时间和内存,订阅的处理函数不一定会被执行,驻留内存有性能开销。2.弱化了对象之间的联系,复杂的情况下可能会导致程序难以跟踪维护和理解。
实现:JS中的事件就是经典的发布-订阅模式的实现
// 订阅1
document.body.addEventListener('click', function() {
console.log('click1');
}, false);
// 订阅2
document.body.addEventListener('click', function() {
console.log('click2');
}, false);
// 发布
document.body.click(); // click1 click2
小A在公司C完成了笔试及面试,小B也在公司C完成了笔试。他们焦急地等待结果,每隔半天就电话询问公司C,导致公司C很不耐烦。
一种解决办法是 AB直接把联系方式留给C,有结果的话C自然会通知AB。这里的“询问”属于显示调用,“留给”属于订阅,“通知”属于发布。
// 观察者
var observer = {
// 订阅集合
subscribes: [],
// 订阅
subscribe: function(type, fn) {
if (!this.subscribes[type]) {
this.subscribes[type] = [];
}
// 收集订阅者的处理
typeof fn === 'function' && this.subscribes[type].push(fn);
},
// 发布 可能会携带一些信息发布出去
publish: function() {
var type = [].shift.call(arguments),
fns = this.subscribes[type];
// 不存在的订阅类型,以及订阅时未传入处理回调的
if (!fns || !fns.length) {
return;
}
// 挨个处理调用
for (var i = 0; i < fns.length; ++i) {
fns[i].apply(this, arguments);
}
},
// 删除订阅
remove: function(type, fn) {
// 删除全部
if (typeof type === 'undefined') {
this.subscribes = [];
return;
}
var fns = this.subscribes[type];
// 不存在的订阅类型,以及订阅时未传入处理回调的
if (!fns || !fns.length) {
return;
}
if (typeof fn === 'undefined') {
fns.length = 0;
return;
}
// 挨个处理删除
for (var i = 0; i < fns.length; ++i) {
if (fns[i] === fn) {
fns.splice(i, 1);
}
}
}
};
// 订阅岗位列表
function jobListForA(jobs) {
console.log('A', jobs);
}
function jobListForB(jobs) {
console.log('B', jobs);
}
// A订阅了笔试成绩
observer.subscribe('job', jobListForA);
// B订阅了笔试成绩
observer.subscribe('job', jobListForB);
// A订阅了笔试成绩
observer.subscribe('examinationA', function(score) {
console.log(score);
});
// B订阅了笔试成绩
observer.subscribe('examinationB', function(score) {
console.log(score);
});
// A订阅了面试结果
observer.subscribe('interviewA', function(result) {
console.log(result);
});
observer.publish('examinationA', 100); // 100
observer.publish('examinationB', 80); // 80
observer.publish('interviewA', '备用'); // 备用
observer.publish('job', ['前端', '后端', '测试']); // 输出A和B的岗位
// B取消订阅了笔试成绩
observer.remove('examinationB');
// A都取消订阅了岗位
observer.remove('job', jobListForA);
observer.publish('examinationB', 80); // 没有可匹配的订阅,无输出
observer.publish('job', ['前端', '后端', '测试']); // 输出B的岗位
7.观察者模式
观察者模式中观察者和目标直接进行交互,而发布订阅模式中统一由调度中心进行处理,订阅者和发布者互不干扰。
最大的区别是调度的地方。虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。
// 观察者
class Observer {
constructor() {
}
update(val) {
}
}
// 观察者列表
class ObserverList {
constructor() {
this.observerList = []
}
add(observer) {
return this.observerList.push(observer);
}
remove(observer) {
this.observerList = this.observerList.filter(ob => ob !== observer);
}
count() {
return this.observerList.length;
}
get(index) {
return this.observerList(index);
}
}
// 目标
class Subject {
constructor() {
this.observers = new ObserverList();
}
addObserver(observer) {
this.observers.add(observer);
}
removeObserver(observer) {
this.observers.remove(observer);
}
notify(...args) {
let obCount = this.observers.count();
for (let index = 0; index < obCount; index++) {
this.observers.get(i).update(...args);
}
}
}
本文固定连接:https://code.zuifengyun.com/2020/06/1753.html,转载须征得作者授权。