设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案.
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。
设计模式优点
- 模式是 解决方法:他们提供固定的解决方法来解决在软件开发中出现的问题
- 模式 重用:一个模式通常反映了一个可以适应自己需要的开箱即用的解决方案。这个特性让它们很健壮。
- 模式 善于表达(优雅的结构和代码解决问题):当我们看到一个提供某种解决方案的模式时,一般有一组结构和词汇可以非常优雅地帮助表达相当大的解决方案。
设计模式原则:SOLID
(1)S – Single Responsibility Principle 单一职责原则
定义:一个类只负责一项职责
优点:
- 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
- 提高类的可读性,提高系统的可维护性;
- 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
(2)O – OpenClosed Principle 开放/封闭原则(开闭原则)
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
问题由来:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
(3)L – Liskov Substitution Principle 里氏替换原则
目的:不要破坏继承体系
定义:子类可以扩展父类的功能,但不能改变父类原有的功能。
它包含以下4层含义:
-
子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
-
子类中可以增加自己特有的方法。
-
当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
-
当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
(4)I – Interface Segregation Principle 接口隔离原则
定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
接口隔离原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
目的:设计接口的时候要精简单一
问题由来:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。
解决方案:将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
(5)D – Dependency Inversion Principle 依赖倒置原则
依赖倒置原则的核心:面向接口编程.
目的:面向接口编程
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
- 面向接口编程,依赖于抽象而不依赖于具体
- 使用方只关注接口而不关注具体类的实现
问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
(6) 迪米特法则
定义:一个对象应该对其他对象保持最少的了解。
问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
解决方案:尽量降低类与类之间的耦合。
目的:降低耦合
设计模式
SN
描述
Creational
根据创建对象的概念分成下面几类。
Class
Factory Method(工厂方法)
通过将数据和事件接口化来构建若干个子类。
Object
Abstract Factory(抽象工厂)
建立若干族类的一个实例,这个实例不需要具体类的细节信息。(抽象类)
Builder (建造者)
将对象的构建方法和其表现形式分离开来,总是构建相同类型的对象。
Prototype(原型)
一个完全初始化的实例,用于拷贝或者克隆。原型模式(prototype)是指用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象。
Singleton(单例)
一个类只有唯一的一个实例,这个实例在整个程序中有一个全局的访问点。
Structural
根据构建对象块的方法分成下面几类。
Class
Adapter(适配器)
将一个类的接口转化为另外一个接口,以满足用户需求,使类之间接口不兼容问题通过适配器得以解决。
Bridge(桥接模式)
将对象的接口从其实现中分离出来,这样对象的实现和接口可以独立的变化。
Composite(组合模式)
通过将简单可组合的对象组合起来,构成一个完整的对象,这个对象的能力将会超过这些组成部分的能力的总和,即会有新的能力产生。
Decorator(装饰器)
动态给对象增加一些可替换的处理流程**。在不改变原对象的基础上,通过对其进行包装扩展**,使原有对象可以满足用户的更复杂需求,而不会影响从这个类中派生的其他对象。
装饰器是一个对类进行处理的函数。装饰器函数的第一个参数,就是所要装饰的目标类。
Facada(外观模式)
一个类隐藏了内部子系统的复杂度,只暴露出一些简单的接口。
Flyweight(享元模式)
一个细粒度对象,用于将包含在其它地方的信息 在不同对象之间高效地共享。
Proxy(代理模式)
一个充当占位符的对象用来代表一个真实的对象。
Behavioral
基于对象间作用方式来分类。
Class
Interpreter(解释器)
将语言元素包含在一个应用中的一种方式,用于匹配目标语言的语法。
Template Method(模板方法)
在一个方法中为某个算法建立一层外壳,将算法的具体步骤交付给子类去做。
Object
Chain of Responsibility(响应链)
一种将请求在一串对象中传递的方式,寻找可以处理这个请求的对象。
Command(命令)
封装命令请求为一个对象,从而使记录日志,队列缓存请求,未处理请求进行错误处理 这些功能称为可能。
Iterator(迭代器)
在不需要直到集合内部工作原理的情况下,顺序访问一个集合里面的元素。提供一种方法顺序一个聚合对象中各个元素,而又不暴露该对象的内部表示。
Mediator(中介者模式)
在类之间定义简化的通信方式,用于避免类之间显式的持有彼此的引用。
Observer(观察者模式)
用于将变化通知给多个类的方式,可以保证类之间的一致性。
State(状态)
当对象状态改变时,改变对象的行为。
Strategy(策略)
将算法封装到类中,将选择和实现分离开来。
Visitor(访问者)
为类增加新的操作而不改变类本身。
常用的几种设计模式
单例模式
定义:保证一个类只有一个实例,并提供它的一个全局访问热点。
特点: ”唯一“和”全局访问“
实现:先判断类的实例存在与否,如果存在则直接返回,如果不存在就创建了再返回。这就确保了一个类只有一个实例对象。
// 利用“闭包”实现
var Singleton = (function() { let instance = null; return function(name) { this.name = name; if(instance){ return instance; } return instance = this; }})();Singleton.prototype.getName = function(){ console.log(this.name);}let winner = new Singleton("winner"); console.log(winner.getName()); //winnerlet sunner = new Singleton("sunner");console.log(sunner.getName()) //winner
//---------------------------
// 代理版单例模式:
// 通过代理的形式,将创建对象的操作和实例判断的操作进行解耦拆分,实现更小粒度的划分,符合”单一职责原则“
class Singlton{ constructor(name) { this.name = name; this.getName(); } getName() { return this.name; }}let ProxySingleton = (function(){ let instance = null; return function(name){ if(instance){ return instance } return instance = new Singlton(name); }})();let winner1 = new ProxySingleton("winner1");let sunner1 = new ProxySingleton("sunner1");console.log(winner1==sunner1);// true
// ProxySingleton()只负责判断实例,Singlton只负责创建对象和赋值;
适用场景:一个单一对象。比如:弹窗,无论点击多少次,弹窗只应该被创建一次。
策略模式
定义:定义一系列的算法,把他们一个个封装起来,并且使他们可以相互替换。
简单定义:就是根据不同的策略来执行不同的方法,类似与if-else分支判断;但是策略模式是用来解决多重条件判断语句的;
目的: 将算法的使用算法的实现分离开来。
组成:至少由两部分组成。
- 第一个部分是一组策略类(可变),策略类封装了具体的算法,并负责具体的计算过程。
- 第二个部分是环境类Context(不变),Context接受客户的请求,随后将请求委托给某一个策略类。要做到这一点,说明Context中要维持对某个策略对象的引用。
适用场景:常用于解决多重判断、根据不同场景执行不同算法。
示例:写一个函数,传入工资基数和绩效等级,返回最终计算的奖金。
**不好的写法:**函数缺乏弹性,如果增加了一种新的绩效等级C,或者把A等级的倍数改成5,那我们必须深入, 违反开放-封闭原则的。
var calculateBonus = function(performanceLevel, salary) {
if (performanceLevel === 'S') {
return salary * 4;
}
if (performanceLevel === 'A') {
return salary * 3;
}
if (performanceLevel === 'B') {
return salary * 2;
}
};
console.log(calculateBonus('B', 20000)); // 输出:40000
console.log(calculateBonus('S', 6000)); // 输出:24000
使用策略模式
var Strategy = { "S" : function(salary) { return salary * 4; }, "A" : function(salary) { return salary * 3; }, "B" : function(salary) { return salary * 2; }}var calculateBonus = function(performanceLevel, salary) { return Strategy[performanceLevel](salary);}console.log(calculateBonus('B', 20000)); // 输出:40000 console.log(calculateBonus('S', 6000)); // 输出:24000
代理模式
定义:为一个对象提供一个代用品或占位符,以便控制对它的访问。
分类:
保护代理:和控制不同权限的对象对目标对象的访问,例如明星都有经纪人作为代理、菜鸟驿站作为收件人的代理。
虚拟代理:把开销大的对象,延迟到真正需要他的时候去创建。例如图片懒加载。
示例:通过菜鸟驿站帮我们代收快递
不使用代理模式:
class getDelivery {
constructor() {
}
//通过gets函数去判断不同的快递类型,然后去执行相对应的操作
gets(a) {
let fn1 = () => {
setTimeout(() => {
//some fns of fn1
console.log(`获取快递有:${a}`)
}, 1000)
}
let fn2 = () => {
setTimeout(() => {
//some fns of fn2
console.log(`获取快递有:${a}`)
}, 2000)
}
let fn3 = () => {
setTimeout(() => {
//some fns of fn3
console.log(`获取快递有:${a}`)
}, 3000)
}
let deliver = {'中通': fn1, 'EMS': fn2, '顺丰': fn3}[name];
return deliver();
}
}
getDelivery.prototype.proxyGets('中通')
使用代理模式:本例中我们并不关心是什么快递,关心的只是接到快递触发的结果,或者说接到快递后,执行的任务才是getDelivery 这个类的核心,至于中间的过程,应该交给专门处理他的类来判别。
class getDelivery {
constructor() {
}
//核心 gets(a) {
console.log(`获取快递有:${a}`)
}
}
class proxy extends getDelivery {
constructor() {
super();
}
//中间过程
proxyGets(name) {
let fn1 = () => {
setTimeout(() => {
//some fns of fn1
super.gets('中通快递')
}, 1000)
}
let fn2 = () => {
setTimeout(() => {
//some fns of fn2
super.gets('韵达') }, 2000)
}
let fn3 = () => {
setTimeout(() => {
//some fns of fn3
super.gets('顺丰')
}, 3000)
}
let deliver = {'中通': fn1, '韵达': fn2, '顺丰': fn3}[name];
return deliver();
}
}
proxy.prototype.proxyGets('中通')
示例2:
图片懒加载的方式:先通过一张loading图占位,然后通过异步的方式加载图片,等图片加载好了再把完成的图片加载到img标签里面。
var imgFunc = (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() {
// 模拟图片加载
imgFunc.setSrc(img.src); }
return {
setSrc: function(src) {
img.src = src;
imgFunc.setSrc('./loading,gif');
}
}
})();
proxyImage.setSrc('./pic.png');
注意:我们的代理和本体接口要保持一致性,如上面proxyImage和myImage都返回一个包含setSrc方法的对象。居于这点我们写代理的时候也有迹可循。
适配器模式
定义: 是将一个类(对象)的接口(方法或属性)转化成客户希望的另外一个接口(方法或属性),适配器模式使得原本由于接口不兼容而不能一起工作的那些类(对象)可以一些工作。
示例: 以前的耳机大多都是圆孔插头,手机的耳机孔也是圆孔的;但是现在手机大多都是typec(扁孔)的,导致不适配;是所以利用一个适配器适配现在的手机。
// 适配器var newHeadset = { flat: function () { console.log("开始听歌"); }}var oldHeadset = { circle: function () { console.log("开始听歌"); }}var oldHeadsetAdapter = { flat: function () { return oldHeadset.circle(); }}var phoneListen = function (headset) { if (headset.flat instanceof Function) { headset.flat(); }}phoneListen(newHeadset);phoneListen(oldHeadsetAdapter);
观察者模式(发布订阅模式)
定义:用户必须订阅才能接收到发布者的消息。三要素:发布者,订阅者和消息发布途径。而对于订阅者来说有时也要分不同类型,给对应的类型发布对应的消息
示例:
class PubAndSub { constructor() { this.events = {}; } pub(type, ...args){ if(this.events[type]) { this.events[type].forEach((item)=>{ item.apply(this, args); }); } } sub(type, event) { if(!this.events[type]) { this.events[type] = []; } this.events[type].push(event); }}var instance = new PubAndSub();instance.sub("test1", function(msg){ console.log("sub1--" + msg);})instance.sub("test2", function(msg){ console.log("sub3--" + msg);});instance.sub("test1", function(msg){ console.log("sub2--" + msg);});instance.pub("test1", "发布");
参考文章
设计模式六大原则
JavaScript设计模式es6(23种)
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!