一、模式的概念
模式可以理解为是一种规范;模式的设计是因为语言不够灵活,不能适应复杂的场景经过时间经验的积累而想出来的办法。
二、设计模式
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。
三、常用的设计模式
1.单例模式
在面向对象语言中,调用一个类的方法之前,必须先将这个类实例化,才能调用类方法。
单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。
单例模式能使得我们不需要每次都需要实例化一次,因为我们使用的对象都是同一个对象。
单例模式:只允许实例化一次的对象类。
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
用一句话来总结就是:在单例模式中,一个类仅有一个实例,并提供一个访问它的全局访问点。这无非是用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象,在JavaScript我们很自然而然就会想到用闭包来解决这个问题。
场景:
单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的 window 对象等。
在 JavaScript 开发中,单例模式的用途同样非常广泛。试想一下,当我们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少 次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。
function Singleton() {
// _instance是自定义的属性
if(!Singleton._instance) {
Singleton._instance = {
name: 'muzidigbig',
age: 25,
say: function() {
alert('我叫'+this.name+',今年'+this.age)
}
}
}
return Singleton._instance;
}
var per1 = new Singleton();
var per2 = new Singleton();
console.log(per1 === per2); // true
console.log(Singleton);
console.log(per2);
per1.say();
2.工厂模式(想要什么样的对象就创造出什么样对象)
工厂模式是一种用来创建对象的设计模式。我们不暴露对象创建的逻辑,而是将逻辑封装在一个函数内,那么这个函数可以成为工厂。
function Factory() {}
Factory.create = function(type) {
switch(type) {
case 'normal':
// 创建normal对象
return new Normal();
break;
case 'gunCarrier':
// 创建gunCarrier对象
return new GunCarrier();
break;
}
}
function Normal() {
console.log('My name is normal');
}
function GunCarrier() {
console.log('My name is gunCarrier');
}
var factory = new Factory.create('gunCarrier');
在上述代码中,Factory.create()就是一个简单的工厂,我们只需要传递相应的参数就可以获取一个实例对象了。
let factory = function (role) {
function User(obj) {
this.name = obj.name;
this.role = obj.role;
}
switch(role) {
case 'superman':
return new User({ name: '平台用户', role: ['主页', '登录页'] })
break;
case 'man':
return new User({ name: '游客', role: ['登录页']})
break;
default:
throw new Error('参数错误')
}
}
let superman = factory('superman');
let man = factory('man');
简单工厂的优点: 你只需要传递一个合法的参数,就可以获取到你想要的对象,而无需知道创建的具体的细节。但是在函数内包含了所有对象的构造函数和判断逻辑的代码, 每次如果需要添加一个对象,那么我们需要新增一个构造函数,当我们需要维护的对象不是上面这2个,而是20个或者更多,那么这个函数将会成为超级函数,使得我们难以维护。所以简单工厂模式只适用于在创建时对象数量少,以及逻辑简单的情况。
工厂方法:
工厂方法模式本意是将实际创造的对象推迟到子类中,这样核心类就变成了抽象类。但是在js中很难像那些传统面向对象语言那样去实现抽象类,所以在js中我们只需要参考他的思想即可。
我们可以把工厂函数看成是一个工厂类。在简单模式我们,我们添加一个新的对象需要修改二处地方,在加入工厂方法模式以后,我们只需要修改一处即可。工厂方法的工厂类,他只做实例化这一件事情。我们只需要修改他的原型类即可。我们采用安全模式创建工厂对象。
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');
什么时候使用工厂模式
工厂模式在应用于以下情况时尤其有用:
1.当我们创建的对象或组件涉及到了很高的复杂度。
2.当我们需要根据所处的环境生成不同的对象实例时。
3.当我们处理含有相同属性的对象或组件时。
4.当创建的对象是其他对象的实例,而且要求它们有一致的API接口时。有利于解耦。
什么时候不应该用工厂模式
如果把工厂模式应用到错误的问题类型,这种模式会给应用程序带来不必要的复杂度。除非为对象创建过程提供接口是我们正在编写的库或框架的设计目标,否则我建议总是使用构造函数来避免不必要的开销。
由于对象创建过程实际上是抽象在一个接口后面的,这也可能会带来单元测试的问题,取决于这个过程可能有多复杂。
3.策略模式
策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
策略模式指的是定义一系列的算法,把它们一个个封装起来。将不变的部分和变化的部分隔开是每个设计模式的主题,策略模式也不例外,策略模式的目的就是将算法的使用与算法的实现分离开来。
一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体 的算法,并负责具体的计算过程。 第二个部分是环境类Context,Context 接受客户的请求,随后 把请求委托给某一个策略类。要做到这点,说明 Context中要维持对某个策略对象的引用。
策略模式的实现并不复杂,关键是如何从策略模式的实现背后,找到封装变化、委托和多态性这些思想的价值。
场景
从定义上看,策略模式就是用来封装算法的。但如果把策略模式仅仅用来封装算法,未免有一点大材小用。在实际开发中,我们通常会把算法的含义扩散开来,使策略模式也可以用来封装 一系列的“业务规则”。只要这些业务规则指向的目标一致,并且可以被替换使用,我们就可以 用策略模式来封装它们。
function Strategy() {
}
Strategy.prototype.tactics = function () {
console.log('基础策略');
}
function StrategyA() {
}
StrategyA.prototype = Object.create(Strategy.prototype);
StrategyA.prototype.tactics = function () {
console.log('骚扰敌人打野');
}
function StrategyB() {
}
StrategyB.prototype = Object.create(Strategy.prototype);
StrategyB.prototype.tactics = function () {
console.log('猥琐发育,别浪');
}
function Go2Victory(strategy1) {
this.strategy = strategy1;
}
Go2Victory.prototype.victory = function() {
this.strategy.tactics();
}
// Go2Victory(传递不同的策略)
var v = new Go2Victory(new StrategyB());
v.victory(); //猥琐发育,别浪
4.适配器模式(兼容模式)
适配器用来解决两个已有接口之间不匹配的问题,它并不需要考虑接口是如何实现,也不用考虑将来该如何修改;适配器不需要修改已有接口,就可以使他们协同工作。
白话解释:
你买了某种电器产品,准备带回家好好感受该款产品的魅力;结果带回家之后准备通电使用的时候,发现该产品仅支持两孔插座,而你家里的电源插座都是三孔插座;这个时候你总不能又跑去电器专卖店退货吧;突然灵机一动,你想起来了家里还有多功能电源插座,而多功能电源插座恰好就是三孔,于是你拿出你的多功能电源插座插上电源插座,再拿你电器产品的电源插座插在多功能插座上面的两孔插座上,开始享受美滋滋的生活;这里的多功能插座就是一个适配器;
function Sagittarius() {
}
Sagittarius.prototype.skill_one = function () {
console.log('人马:刚刚施展了【1】');
}
Sagittarius.prototype.skill_two = function () {
console.log('人马:刚刚施展了【2】');
}
Sagittarius.prototype.skill_three = function () {
console.log('人马:刚刚施展了【3】');
}
Sagittarius.prototype.skill_four = function () {
console.log('人马:刚刚施展了【4】');
}
function GuanYu() {
this.s = new Sagittarius();
}
GuanYu.prototype.skill_1 = function () {
this.s.skill_one()
}
GuanYu.prototype.skill_2 = function () {
this.s.skill_two()
}
GuanYu.prototype.skill_3 = function () {
this.s.skill_three()
}
var guanYu = new GuanYu();
guanYu.skill_2(); // 人马:刚刚施展了【2】
个人认为适配器模式其实是一种亡羊补牢式的设计模式,如果在项目开发的开始阶段我们就知道我们期待的数据格式或者方法名等,我们就可能永远都用不到适配器模式;但是项目的迭代往往是不可预期的,当项目迭代之后数据格式或者方法名发生变化之后,我们通常可以使用适配器模式来进行适配解决;当然了,最好的解决办法就是项目开发过程中前后端协商讨论数据格式、文件名等代码规范,这样是对项目的开发效率是会有很大的提升的;
5.观察者模式(一旦状态发生改变,相关的都会得到通知。MVC)
观察者模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
注意:有些人认为观察者模式就是发布订阅模式,但实际上观察者模式和发布订阅模式是有区别的。
区别:观察者模式只有两个,一个是观察者一个是被观察者。发布订阅模式不一样,发布订阅模式还有一个中间层,发布订阅模式的实现是,发布者通知给中间层 => 中层接受并通知订阅者 => 订阅者收到通知并发生变化
<button class="btnFirst">first blood</button>
<button class="btnDouble">double kill</button>
function Message() {
this._list = [];
this.msg = 'hello';
}
Message.prototype.attach = function(hero) {
this._list.push(hero)
}
Message.prototype.notify = function() {
for(var i = 0; i < this._list.length; i++) {
this._list[i].update();
}
}
Message.prototype.setState = function(msg) {
this.msg = msg;
this.notify();
}
Message.prototype.getState = function() {
return this.msg;
}
function Hero(name, msg) {
this.name = name;
this.msg = msg;
}
Hero.prototype.update = function() {
console.log(this.name + ' received:' + this.msg.getState());
}
var msg = new Message();
var muzi = new Hero('muzidigbig', msg);
var hz = new Hero('黄忠', msg);
var ys = new Hero('亚瑟', msg);
msg.attach(muzi);
msg.attach(hz);
msg.attach(ys);
document.querySelector('.btnFirst').onclick = function() {
msg.setState('first blood')
};
document.querySelector('.btnDouble').onclick = function() {
msg.setState('double kill')
};