概念
- 设计模式就是前人总结出来的一套更加容易阅读、维护以及复用的写代码的方式
SOLID 五大设计原则
S(Single responsibility principle)——单一职责原则
- 一个程序只做一件事
- 如果功能过于复杂就拆分开,每个部分保持独立
O(Open Closed Principle)——开放封闭原则
- 面向对象的核心
- 对扩展开放,对修改封闭
- 增加需求时,扩展新代码,而非修改已有代码
L(Liskov Substitution Principle, LSP)——李氏置换原则
- 子类能覆盖父类
- 父类能出现的地方子类就能出现
- js 中使用功能较少(弱类型 & 继承使用较少)
I (Interface Segregation Principle)——接口独立原则
- 保持接口的单一独立
- js 中没有接口概念(typescript例外)
- 类似单一职责原则,这里更关注接口
D(Dependence Inversion Principle ,DIP)——依赖倒置原则
- 面向接口编程,依赖于抽象而不依赖于具体
- 使用方只关注接口而不关注具体类的实现
- js 中使用较少(没有接口概念,弱类型)
设计模式的类型
创建型模式(Creational Patterns)
- 创建型模式就是创建对象的模式,抽象了实例化的过程,即对创建对象的过程进行了封装,作为客户程序仅仅需要去使用对象,而不再关心创建对象过程中的逻辑
- 这里有6个具体的创建型模式可供研究,它们分别是:
- 简单工厂模式(Simple Factory)它不是GoF总结出来的23种设计模式之一
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
- 建造者模式(Builder)
- 原型模式(Prototype)
- 单例模式(Singleton)
结构型模式(Structural Patterns)
- 结构型模式就是组装现有的类并设计它们的交互方式,也就是设计对象的结构、继承和依赖关系
- 对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等。对象结构的设计很容易体现出设计人员水平的高低
- 这里有7个具体的结构型模式可供研究,它们分别是:
- 外观模式/门面模式(Facade门面模式)
- 适配器模式(Adapter)
- 代理模式(Proxy)
- 装饰模式(Decorator)
- 桥梁模式/桥接模式(Bridge)
- 组合模式(Composite)
- 享元模式(Flyweight)
行为型模式(Behavioral Patterns)
- 行为模式描述了对象和类的模式,以及它们之间的通信模式
- 行为模式用于识别对象之间常见的交互模式并加以实现,提升它们之间的协作效率
- 这里有11个具体的行为型模式可供研究,它们分别是:
- 模板方法模式(Template Method)
- 观察者模式(Observer)
- 状态模式(State)
- 策略模式(Strategy)
- 职责链模式(Chain of Responsibility)
- 命令模式(Command)
- 访问者模式(Visitor)
- 调停者模式(Mediator)
- 备忘录模式(Memento)
- 迭代器模式(Iterator)
- 解释器模式(Interpreter)
三者之间的区别和联系
- 创建型模式为其他两种模式使用提供了环境
- 结构型模式侧重于接口的使用,它做的一切工作都是对象或是类之间的交互,提供一个门
- 行为型模式顾名思义,侧重于具体行为
- 总结一句话就是:创建型模式提供生存环境,结构型模式提供生存理由,行为型模式提供如何生存
前端常用的设计模式
创建型模式之工厂模式(Factory Method)
- 工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类,而子类可以重写接口方法以便创建的时候指定自己的对象类型
- 将 new 操作进行简单的封装,遇到 new 我们就应该考虑是否用工厂模式
- 相对于复杂的创建对象的过程我们只关心结果,符合开闭原则,扩展性高
- JQuery 的 $() 就是一个工厂函数,它根据传入参数的不同创建元素或者去寻找上下文中的元素,创建成相应的 jQuery 对象
class jQuery {
constructor(selector) {
super(selector)
}
add() {
}
// 此处省略若干API
}
window.$ = function(selector) {
return new jQuery(selector)
}
创建型模式之建造者模式( Builder pattern)
- 将一个复杂的对象分解成多个简单的对象来进行构建,将复杂的构建层与表现层分离,使相同的构建过程可以创建不同的表示模式
- 建造的封装性比较好,创建对象和构建过程解藕,并且很容易扩展
- 分步创建一个复杂的对象,解藕封装过程和具体创建组件
function Fangzi(){
this.woshi = "";
this.keting = "";
this.chufang = "";
}
function Baogongtou(){
this.jianfangzi = function(gongren){
gongren.jian_woshi();
gongren.jian_keting();
gongren.jian_chufang();
}
}
function Gongren(){
this.jian_woshi = function(){
console.log("卧室建好了!");
}
this.jian_keting = function(){
console.log("客厅建好了!");
}
this.jian_chufang = function(){
console.log("厨房建好了!");
}
this.wangong = function(){
var fangzi = new Fangzi();
fangzi.woshi = "ok";
fangzi.keting = "ok";
fangzi.chufang = "ok";
return fangzi;
}
}
let gongren = new Gongren();
let baogongtou = new Baogongtou();
//卧室建好了!
//客厅建好了!
//厨房建好了!
baogongtou.jianfangzi(gongren);
var my_fangzi = gongren.wangong();
/*
Fangzi={
chufang: "ok"
keting: "ok"
woshi: "ok"
}
*/
console.log(my_fangzi);
创建型模式之原型模式(Prototype pattern)
- 创建一个共享的原型,通过拷贝这个原型来创建新的类,用于创建重复的对象带来性能上的提升
- Object.create() 方法会使用指定的原型对象及其属性去创建一个新的对象。
// 因为不是构造函数,所以不用大写
var someCar = {
drive: function () { },
name: '红旗'
};
// 使用Object.create创建一个新车x
var anotherCar = Object.create(someCar);
anotherCar.name = '中华';
- 如果你希望自己去实现原型模式,而不直接使用Object.create。你可以使用一下代码实现。
var vehiclePrototype = {
init: function (carModel) {
this.model = carModel;
},
getModel: function () {
console.log('车辆模具是:' + this.model);
}
};
function vehicle(model) {
function F() { };
F.prototype = vehiclePrototype;
var f = new F();
f.init(model);
return f;
}
var car = vehicle('福特Escort');
car.getModel();
创建型模式之单例模式(Singleton Pattern)
- 一个类只有一个实例,并提供一个访问它的全局访问点
- 隐藏 Class 的构造函数,避免多次实例化,通过暴露一个 getInstance() 方法来创建 / 获取唯一实例
- 优点:
- 划分命名空间,减少全局变量
- 增强模块性,把自己的代码组织在一个全局变量名下,放在单一位置,便于维护
- 且只会实例化一次。简化了代码的调试和维护
- 缺点:由于单例模式提供的是一种单点访问,所以它有可能导致模块间的强耦合 从而不利于单元测试。无法单独测试一个调用了来自单例的方法的类,而只能把它与那个单例作为一个单元一起测试。
- 登录框、vuex 和 redux 中的 store
// 单例构造器
const FooServiceSingleton = (function () {
// 隐藏的Class的构造函数
function FooService() {}
// 未初始化的单例对象
let fooService;
return {
// 创建/获取单例对象的函数
getInstance: function () {
if (!fooService) {
fooService = new FooService();
}
return fooService;
}
}
})();
const fooService1 = FooServiceSingleton.getInstance();
const fooService2 = FooServiceSingleton.getInstance();
console.log(fooService1 === fooService2); // true
结构型模式之外观模式(Facade Pattern)
- 为子系统中的一组接口提供一个统一的高层接口,使子系统更加容易使用
- 兼容浏览器事件的绑定:
let addMyEvent = function (el, ev, fn) {
if (el.addEventListener) {
el.addEventListener(ev, fn, false)
} else if (el.attachEvent) {
el.attachEvent('on' + ev, fn)
} else {
el['on' + ev] = fn
}
};
结构型模式之适配器模式(Adapter Pattern)
- 将一个类的接口转化为另外一个接口,以满足用户需求,解决类之间接口不兼容问题
- 与代理模式类似它提供一个不同的接口,而代理模式提供一模一样的接口
- 比如你现在正在用一个自定义的 js 库,里面有个根据 id 获取节点的方法 $id()。有天你觉得 jquery 里的 $ 实现得更酷, 但你又不想让你的工程师去学习新的库和语法, 那一个适配器就能让你完成这件事情
$id = function( id ){
return jQuery( '#' + id );
}
结构型模式之代理模式(Proxy Pattern)
- 将一个对象的访问交给另外一个代理对象来操作
- 代理模式能将代理对象与被调用对象分离,降低了系统的耦合度。代理模式在客户端和目标对象之间起到一个中介作用,这样可以起到保护目标对象的作用
- 代理对象可以扩展目标对象的功能;通过修改代理对象就可以了,符合开闭原则;
- HTML 元素事件代理,ES6 的 Proxy
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
let ul = document.querySelector('#ul');
ul.addEventListener('click', event => {
console.log(event.target);
});
</script>
结构型模式之装饰者模式(Decorator Pattern)
- 动态的给某一个对象添加一些额外的职责,是一种实现继承的替代方案
- 在不改变原对象的基础上,通过对其进行包装扩展,使原有对象可以满足用户的更复杂需求,而不会影响从这个类中派生的其他对象
class Cellphone {
create() {
console.log('生成一个手机')
}
}
class Decorator {
constructor(cellphone) {
this.cellphone = cellphone
}
create() {
this.cellphone.create()
this.createShell(cellphone)
}
createShell() {
console.log('生成手机壳')
}
}
// 测试代码
let cellphone = new Cellphone()
cellphone.create()
console.log('------------')
let dec = new Decorator(cellphone)
dec.create()
结构型模式之桥接模式(Bridge Pattern)
- 将抽象部分与它的实现部分分离,使他们都可以独立的变化
- 有助于独立的管理各组成部分,把抽象化与实现化解藕,提高可扩充性
class Color {
constructor(name){
this.name = name
}
}
class Shape {
constructor(name,color){
this.name = name
this.color = color
}
draw(){
console.log(`${this.color.name} ${this.name}`)
}
}
//测试
let red = new Color('red')
let yellow = new Color('yellow')
let circle = new Shape('circle', red)
circle.draw()
let triangle = new Shape('triangle', yellow)
triangle.draw()
结构型模式之组合模式(Composite Pattern)
- 用小的子对象来构建更大的对象,同时这些小的子对象也许是由更小的孙对象构成
- 组合模式将对象组合成树形结构,可以很方便的描述对象 部分-整体 的树形结构,利用对象的多态性统一对待组合对象和单个对象
class TrainOrder {
create () {
console.log('创建火车票订单')
}
}
class HotelOrder {
create () {
console.log('创建酒店订单')
}
}
class TotalOrder {
constructor () {
this.orderList = []
}
addOrder (order) {
this.orderList.push(order)
return this
}
create () {
this.orderList.forEach(item => {
item.create()
})
return this
}
}
// 可以在购票网站买车票同时也订房间
let train = new TrainOrder()
let hotel = new HotelOrder()
let total = new TotalOrder()
total.addOrder(train).addOrder(hotel).create()
结构型模式之享元模式(Flyweight Pattern)
- 主要用于减少创建对象的数量,以减少内存占用和提高性能
- 享元模式要求将对象的属性划分为内部状态和外部状态(状态在这里通常指属性),其目标是尽量减少共享对象的数量
//雇佣模特
var HireModel = function(sex){
//内部状态是性别
this.sex = sex;
};
HireModel.prototype.wearClothes = function(clothes){
console.log(this.sex+"穿了"+clothes);
};
//工厂模式,负责造出男女两个模特
var ModelFactory = (function(){
var cacheObj = {};
return {
create:function(sex){
//根据sex分组
if(cacheObj[sex]){
return cacheObj[sex];
} else {
cacheObj[sex] = new HireModel(sex);
return cacheObj[sex];
}
}
};
})();
//模特管理
var ModelManager = (function(){
//容器存储:1.共享对象 2.外部状态
var vessel = {};
return {
add:function(sex,clothes,id){
//造出共享元素:模特
var model = ModelFactory.create(sex);
//以id为键存储所有状态
vessel[id] = {
model:model,
clothes:clothes
};
},
wear:function(){
for(var key in vessel){
//调用雇佣模特类中的穿衣服方法。
vessel[key]['model'].wearClothes(vessel[key]['clothes']);
}
}
};
})();
/*******通过运行时间测试性能**********/
for(var i=0;i<100;i++){
ModelManager.add('male','第'+i+'款男衣服',i);
ModelManager.add('female','第'+i+'款女衣服',i);
}
ModelManager.wear();
行为型模式之模版方法模式(Template Method)
- 模版方法模式由两部分组成:抽象父类和具体的实现子类
- 再抽象父类中封装了子类的算法框架,包括实现一些公共方法和封装子类所有方法的执行顺序
- 子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法
- 模版方法模式提取了公共代码部分易于维护
class Beverage {
constructor({brewDrink, addCondiment}) {
this.brewDrink = brewDrink
this.addCondiment = addCondiment
}
/* 烧开水,共用方法 */
boilWater() { console.log('水已经煮沸=== 共用') }
/* 倒杯子里,共用方法 */
pourCup() { console.log('倒进杯子里===共用') }
/* 模板方法 */
init() {
this.boilWater()
this.brewDrink()
this.pourCup()
this.addCondiment()
}
}
/* 咖啡 */
const coffee = new Beverage({
/* 冲泡咖啡,覆盖抽象方法 */
brewDrink: function() { console.log('冲泡咖啡') },
/* 加调味品,覆盖抽象方法 */
addCondiment: function() { console.log('加点奶和糖') }
})
coffee.init()
行为型模式之观察者模式(Observer Pattern)
- 通常又被称为发布-订阅者模式或消息机制
- 被观察对象(subject)维护一组观察者(observer),当被观察对象状态改变时,通过调用观察者的某个方法将这些变化通知到观察者
- 它定义了对象间的一种一对多的依赖关系,只要当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,解决了主体对象与观察者之间功能的耦合,即一个对象状态改变给其他对象通知的问题
- 场景:DOM事件、Vue 响应式
document.body.addEventListener('click', function() {
console.log('hello world!');
});
document.body.click()
行为型模式之状态模式(State Pattern)
- 允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类
- 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为
- 一个操作中含有大量的分支语句,而且这些分支语句依赖于该对象的状态。状态通常为一个或多个枚举常量的表示
class SuperMarry {
constructor() {
this._currentState = []
this.states = {
jump() {console.log('跳跃!')},
move() {console.log('移动!')},
shoot() {console.log('射击!')},
squat() {console.log('蹲下!')}
}
}
change(arr) { // 更改当前动作
this._currentState = arr
return this
}
go() {
console.log('触发动作')
this._currentState.forEach(T => this.states[T] && this.states[T]())
return this
}
}
new SuperMarry()
.change(['jump', 'shoot'])
.go() // 触发动作 跳跃! 射击!
.go() // 触发动作 跳跃! 射击!
.change(['squat'])
.go() // 触发动作 蹲下!
行为型模式之策略模式(Strategy Pattern)
- 定义一系列算法,把它们一个个封装起来,并且使他们可以相互替换
- 目的就是将算法的使用和算法的实现分离开来
- 一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体 的算法,并负责具体的计算过程。 第二个部分是环境类Context,Context 接受客户的请求,随后 把请求委托给某一个策略类。要做到这点,说明 Context中要维持对某个策略对象的引用。
- 优点
- 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
- 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的strategy中,使得它们易于切换,易于理解,易于扩展。
- 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
- 在策略模式中利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案。
- 缺点
- 增加许多策略类或者策略对象,但实际上这比把它们负责的 逻辑堆砌在 Context 中要好。
- 要使用策略模式,必须了解所有的 strategy,必须了解各个 strategy 之间的不同点, 这样才能选择一个合适的 strategy。
var strategies = {
"S": function( salary ){
return salary * 4;
},
"A": function( salary ){
return salary * 3;
},
"B": function( salary ){
return salary * 2;
}
};
var calculateBonus = function( level, salary ){
return strategies[ level ]( salary );
};
console.log( calculateBonus( 'S', 20000 ) ); // 输出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000
行为型模式之职责链模式(Chain of Responsibility Pattern)
- 使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止
- 场景:作用域链、原型链
// 请假审批,需要组长审批、经理审批、总监审批
class Action {
constructor(name) {
this.name = name
this.nextAction = null
}
setNextAction(action) {
this.nextAction = action
}
handle() {
console.log( `${this.name} 审批`)
if (this.nextAction != null) {
this.nextAction.handle()
}
}
}
let a1 = new Action("组长")
let a2 = new Action("经理")
let a3 = new Action("总监")
a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()
行为型模式之命令模式(Command Pattern)
- 用于将一个请求封装成为对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及执行可撤销的操作。
- 也就是说:该模式旨在将函数的调用,请求和操作封装成为一个单一的对象,然后对这个对象进行一系列的处理。
命令模式中的命令:指的是一个指向某些特定事情的指令。
// 接收者类
class Receiver {
execute() {
console.log('接收者执行请求')
}
}
// 命令者
class Command {
constructor(receiver) {
this.receiver = receiver
}
execute () {
console.log('命令');
this.receiver.execute()
}
}
// 触发者
class Invoker {
constructor(command) {
this.command = command
}
invoke() {
console.log('开始')
this.command.execute()
}
}
// 仓库
const warehouse = new Receiver();
// 订单
const order = new Command(warehouse);
// 客户
const client = new Invoker(order);
client.invoke()
行为型模式之访问者模式(Visitor Pattern)
- 针对于对象结构的元素,定义在不改变该对象的前提下访问结构中元素如的新方法。
var Visitor = (function() {
return {
splice: function(){
var args = Array.prototype.splice.call(arguments, 1)
return Array.prototype.splice.apply(arguments[0], args)
},
push: function(){
var len = arguments[0].length || 0
var args = this.splice(arguments, 1)
arguments[0].length = len + arguments.length - 1
return Array.prototype.push.apply(arguments[0], args)
},
pop: function(){
return Array.prototype.pop.apply(arguments[0])
}
}
})()
var a = new Object()
console.log(a.length)
Visitor.push(a, 1, 2, 3, 4)
console.log(a.length)
Visitor.push(a, 4, 5, 6)
console.log(a.length)
Visitor.pop(a)
console.log(a)
console.log(a.length)
Visitor.splice(a, 2)
console.log(a)
行为型模式之备忘录模式(Memento Pattern)
- 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
- 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
//备忘类
class Memento{
constructor(content){
this.content = content
}
getContent(){
return this.content
}
}
// 备忘列表
class CareTaker {
constructor(){
this.list = []
}
add(memento){
this.list.push(memento)
}
get(index){
return this.list[index]
}
}
// 编辑器
class Editor {
constructor(){
this.content = null
}
setContent(content){
this.content = content
}
getContent(){
return this.content
}
saveContentToMemento(){
return new Memento(this.content)
}
getContentFromMemento(memento){
this.content = memento.getContent()
}
}
//测试代码
let editor = new Editor()
let careTaker = new CareTaker()
editor.setContent('111')
editor.setContent('222')
careTaker.add(editor.saveContentToMemento())
editor.setContent('333')
careTaker.add(editor.saveContentToMemento())
editor.setContent('444')
console.log(editor.getContent()) //444
editor.getContentFromMemento(careTaker.get(1))
console.log(editor.getContent()) //333
editor.getContentFromMemento(careTaker.get(0))
console.log(editor.getContent()) //222
行为型模式之迭代器模式(Iterator Pattern)
- 提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示
- 为遍历不同数据结构的 “集合” 提供统一的接口;能遍历访问 “集合” 数据中的项,不关心项的数据结构
- ES6 的 Iterator 迭代器
let arr = ['a', 'b', 'c'];
let iterator = arr[Symbol.iterator]();
iterator.next(); // { value: 'a', done: false }
iterator.next(); // { value: 'b', done: false }
iterator.next(); // { value: 'c', done: false }
iterator.next(); // { value: undefined, done: true }
// Iterator 实现
let iteratorFun = function(arr) {
let nextIndex = 0
return {
next: function() {
return nextIndex < arr.length ? {
value: arr[nextIndex++],
done: false
} : {
value: undefined,
done: true
}
}
}
}
let it = iteratorFun(arr)
console.log(it.next()) // {value: "a", done: false}
console.log(it.next()) // {value: "b", done: false}
console.log(it.next()) // {value: "c", done: false}
console.log(it.next()) // {value: undefined, done: true}
行为型模式之解释器模式(Interpreter Pattern)
- 给定一个语言, 定义它的文法的一种表示,并定义一个解释器, 该解释器使用该表示来解释语言中的句子
- 易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法
- 执行效率较低,在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度慢。对于复杂的文法比较难维护
class Context {
constructor() {
this._list = []; // 存放 终结符表达式
this._sum = 0; // 存放 非终结符表达式(运算结果)
}
get sum() {
return this._sum;
}
set sum(newValue) {
this._sum = newValue;
}
add(expression) {
this._list.push(expression);
}
get list() {
return [...this._list];
}
}
class PlusExpression {
interpret(context) {
if (!(context instanceof Context)) {
throw new Error("TypeError");
}
context.sum = ++context.sum;
}
}
class MinusExpression {
interpret(context) {
if (!(context instanceof Context)) {
throw new Error("TypeError");
}
context.sum = --context.sum;
}
}
/** 以下是测试代码 **/
const context = new Context();
// 依次添加: 加法 | 加法 | 减法 表达式
context.add(new PlusExpression());
context.add(new PlusExpression());
context.add(new MinusExpression());
// 依次执行: 加法 | 加法 | 减法 表达式
context.list.forEach(expression => expression.interpret(context));
console.log(context.sum);