@Bios
2018-12-10T08:34:12.000000Z
字数 15347
阅读 1401
js
内容来自:慕课网 javascript设计模式课程!!

class People {// 属性constructor(name, age) {this.name = name;this.age = age;}// 方法eat() {alert(`${this.name} eat something`)}speak() {alert(`My Name is ${this.name}`)}}
// 创建实例对象let zhang = new People('zhang', 20)zhang.eat()zhang.speak()
class Student extends People{constructor(name, age, number){super(name, age);this.number = number;}study() {alert(`${this.name} study`);}}
People是父类,公共的,不仅仅服务于Student
继承可以将公共方法抽离出来,提高复用,减少冗余
ES6尚不支持,可以通过TypeScript演示
class People1 {nameageprotected weightconstructor(name, age) {this.name = namethis.age = agethis.weight = 120}eat() {alert(`${this.name} eat something`)}speak() {alert(`My Name is ${this.name}`)}}class Student1 extends People{numberprivate girlfriend // 私有的constructor(name, age, number){super(name, age);this.number = number;this.girlfriend = 'xiaoli'}study() {alert(`${this.name} study`);}getWeight() {alert(`${this.weight}`)}}let xiaoming = new Student1('xiaoming',10,'A1')xiaoming.getWeight()alert(xiaomimg.girlfriend) // 会报错,不能访问
优点:
_开头的属性和方法是private
按照哪一种思路或者标准来实现功能
功能相同,可以有不同设计方案来实现
伴随着需求增加,设计的作用才能体现出来

| 简写 | 全称 | 中文 |
|---|---|---|
| SRP | The Single Responsibility Principle | 单一职责原则 |
| OCP | The Open Closed Principle | 开放封闭原则 |
| LSP | The Liskov Substitution Principle | 里氏替换原则 |
| ISP | The Interface Segregation Principle | 接口分离原则 |
| DIP | The Dependency Inversion Principle | 依赖倒置原则 |
设计模式(design pattern)是一套被反复使用、思想成熟、经过分类和无数实战设计经验总结的。
使用设计模式是为了让系统代码可复用、可扩展、可解耦、更容易被人理解且能保证代码可靠性。
创建型
结构型
行为型
工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型(抽象工厂)。
将 new 操作单独封装,遇到new时,就要考虑是否该用工厂模式
模式作用:
注意事项:
UML

// productclass jQuery {constructor(selector) {let slice = Array.prototype.slicelet dom = slice.call(document.querySelectorAll(selector))let len = dom ? dom.length : 0for (let i = 0; i < len; i++) {this[i] = dom[i]}this.length = lenthis.selector = selector || ''}append(node) {}addClass(name) {}html(data) {}// 此处省略若干 API}// 工厂window.$ = function (selector) {return new jQuery(selector)}
简单工厂模式:又叫静态工厂方法,由一个工厂对象决定创建某一种产品对象类的示例。主要用来创建同一类对象。
去KFC点一个汉堡,服务员给你的是个汉堡,而不是牛肉、面粉、佐料...
// KFC的类class KFC {// 做汉堡makeHbg () {// ...繁琐的工序console.log('汉堡一个')}// 炸鸡腿makeChk () {// ...繁琐的工序console.log('鸡腿一个')}}// 某一家KFC的店铺let kfcFactory = function (food) {let kfc = new KFC()switch (food) {case hamburger:return kfc.makeHbg()break;case chicken:return kfc.makeChk()break;}}// 对于顾客来说,他只需要‘传入’他需要的东西就好了,不用关心汉堡是怎么做出来的kfcFactory(hamburger); // '汉堡一个'
工厂方法:通过对产品类的抽象使其创建业务主要负责用于创建多类产品的实例。
现在有个工厂来生成所有的共享单车,模拟一下工厂模式。
// 别较真 栗子不好吃 理解这种方式就行let Ofo = function() {this.name = 'ofo'// ... 省略每个品牌的独有的属性方法(function() {let div = document.createElement('div')div.innerHTML = '这是一辆ofo单车'document.getElementById('box').appendChild(div)})()}let Mobike = function() {this.name = 'Mobike'// ... 省略每个品牌的独有的属性方法(function() {let div = document.createElement('div')div.innerHTML = '这是一辆Mobike单车'document.getElementById('box').appendChild(div)})()}let Hello = function() {this.name = 'hello'// ... 省略每个品牌的独有的属性方法(function() {let div = document.createElement('div')div.innerHTML = '这是一辆hello单车'document.getElementById('box').appendChild(div)})()}let Blue = function() {this.name = 'blue'// ... 省略每个品牌的独有的属性方法(function() {let div = document.createElement('div')div.innerHTML = '这是一辆blue单车'document.getElementById('box').appendChild(div)})()}let Bikefactory = function (type) {switch (type) {case 'Ofo':return new Ofo()break;case 'Mobike':return new Mobike()break;case 'Hello':return new Hello()break;case 'Blue':return new Blue()break;}}
安全模式类是为了解决错误使用类而造成的错误。
var Demo = function() {}Demo.prototype={show: function() {console.log('Hello Demo')}}// 正确使用var d = new Demo()d.show();// 错误使用var d = Demo()d.show(); // Uncaught TypeError:Cannot read property 'show' of undefined
为了避免这类错误的发生,在构造函数开始时先判断当前对象this指代的是不是类(Demo)。
var Demo = function () {if (!(this instanceof Demo)) {return new Demo()}}var d =Demo();d.show(); // 'Hello Demo'
上面这样写,我们发现当共享单车的种类越来越多,需要添加新的共享单车时,就需要修改两处的代码,所以可以对它进行修改,按工厂模式方法来做。
let Bikefactory = function (name) {// 使用完全模式if (this instanceof Bikefactory) {let s = new this[type](name)return s;} else {return new Bikefactory(name)}}Bikefactory.prototype = {Ofo: function() {this.name = 'ofo'// ... 省略每个品牌的独有的属性方法(function() {let div = document.createElement('div')div.innerHTML = '这是一辆ofo单车'document.getElementById('box').appendChild(div)})()},Mobike: function() {....},...}
抽象工厂模式:通过对类的工厂抽象使其业务用于对产品类簇的创建,而不负责创建某一类产品的实例。
在JavaScript中abstract是一个保留字,所以目前来说还不能像传统的面向对象语言那样轻松的创建抽象类。抽象类是一种声明但不能使用的类,当你使用时就会报错。
// 抽象类let Car = function() {}Car.prototype = {run: function() {return new Error('抽象方法不能调用!')}}
// 在ES6中定义抽象类class Car {constructor() {if (new.target === Car) {throw new Error ('抽象类不能实例化!')}}}
定义的Car中有一个run方法,继承与Car的子类都会拥有直接使用,需要重写。这也是抽象类的一个作用,即定义一个产品簇,并声明一些必备的方法,如果子类中没有重写这些方法,直接使用就会抛出错误。
var XMLHttpFactory = function() {}XMLHttpFactory.prototype = {// 如果真的要调用这个方法会抛出一个错误,它不能被实例化,只能用来派生子类createFactory: function () {throw new Error('This is an abstract class')}}// 经典继承var XHRHandler = function () {XMLHttpFactory.call(this)}XHRHandler.prototype = new XMLHttpFactory()XHRHandler.prototype.constructor = XHRHandlerXHRHandler.prototype.createFactory = function() {var XMLHttp = null;if(window.XMLHttpRequest) {XMLHttp = new XMLHttpRequest()} else if (window.ActiveXObject){XMLHttp = new ActiveXObject("Microsoft.XMLHttp")}return XMLHttp;}
如果没有看明白经典继承部分的代码,可以去看看原型,原型链,call/apply。
用ES6的语法来实现一下抽象工厂,还是用共享单车的例子来改写一下:
// 别较真 栗子不好吃 理解这种方式就行class Bike {constructor(name) {if (new.target === Bike) {throw new Error('抽象类不能实例化!')}this.name = name;// ... 此处省略100行}// ... 此处省略100行init () {return new Error('抽象方法不能调用!')}}class Ofo extends Bike {constructor(name) {super('ofo')this.name = name;// ... 此处省略100行}// ... 此处省略100行init () {console.log('这是一辆ofo单车!')}}class Mobike extends Bike {...}class Hello extends Bike {...}class Blue extends Bike {...}// ... 更多的单车let Bikefactory = function (name) {switch (name) {case 'Ofo':return new Ofo()break;case 'Mobike':return new Mobike()break;case 'Hello':return new Hello()break;case 'Blue':return new Blue()break;}}
抽象工厂只留了一个“口子”,它不做具体的事,由它的子类,根据自身情况重写方法。
系统中被唯一使用,一个类只有一个实例,实现方法一般是先判断实例是否存在,如果存在就返回,不存在就创建再返回。
在JavaScript里,单例作为空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。
模式作用:
- 模块间通信
- 系统中某个类的对象只能存在一个
- 保护自己的属性和方法
注意事项:
- 注意this的使用
- 闭包容易造成内存泄漏,不需要的赶快干掉
- 注意new的成本
java中的单例模式

JavaScript中的单例模式
class SingleObject {login() {console.log('login')}}// 利用闭包实现了私有变量SingleObject.getInstance = (fucntion () {let instancereturn function () {if (!instance) {instance = new SingleObject()}return instance}})()let obj1 = SingleObject.getInstance()obj1.login()let obj2 = SingleObject.getInstance()obj2.login()// 两者是否相等console.log(obj1 === obj2)// js弱类型,没有私有方法,使用者还是可以直接new 一个 SingleObject,也会有 login方法console.log('------------分割线------------')let obj3 = new SingleObject()obj3.login()console.log('obj1===obj3',obj1 === obj3) // false 不是单例
最简单的单例模式,就是对象。在 JavaScript 中 定义一个对象(Object),那么它的属性,就只能通过它自己调用。就算两个不同的对象,有相同的属性名,也不能相互调用,保护了自己属性。
登录框 单例
class LoginForm {constructor() {this.state = 'hide'}show() {if (this.state === 'show') {alert('已经显示')return}this.state = 'show'console.log('登录框已显示')}hide() {if (this.state === 'hide') {alert('已经隐藏')return}this.state = 'hide'console.log('登录框已隐藏')}}LoginForm.getInstance = (function () {let instancereturn function () {if (!instance) {instance = new LoginForm();}return instance}})()// 一个页面中调用登录框let login1 = LoginForm.getInstance()login1.show()// login1.hide()// 另一个页面中调用登录框let login2 = LoginForm.getInstance()login2.show()// 两者是否相等console.log('login1 === login2', login1 === login2)
适配器模式(Adapter)是将一个类(对象)的接口(方法或属性)转换成客户希望的另外一个接口(方法或属性),适配器模式使得原本由于接口不兼容而不能一起工作的那些类(对象)可以一起工作。[旧接口格式和使用者不兼容,中间加一个适配器转换接口。]

UML

模式作用:
- 使用一个已经存在的对象,但其方法或接口不符合你的要求。
- 创建一个可复用的对象,该对象可以与其他不相关或不可见的对象协同工作。
- 使用已经存在的一个或多个对象,但是不能进行继承已匹配它的接口。
注意事项:
- 与代理模式的区别,代理模式是不改变原接口,适配是原接口不符合规范
//谷歌地图show方法var googleMap = {googlShow: function() {console.log("谷歌地图");}};//百度地图show方法var baiduMap = {baiduShow: function() {console.log("百度地图");}};//渲染地图函数var renderMap=function(map){if(map.show instanceof Function){map.show();}};renderMap(googleMap);//输出:开始渲染谷歌地图renderMap(baiduMap);//输出:开始渲染百度地图
适配器模式还有数据的适配,在现在开发中,各种UI框架层出不穷(elementUI),每个框架中对组件数据格式的定义不一样,后台返回的数据也不能完全按照框架的格式,这时作为前端程序猿,我们就需要把后台返回的数据做一次修改,以适应框架。这就是设配器的想法,不要听到设计模式就很恼火,说不定你每天都在用!!!

为对象添加新功能;不改变其原有的结构和功能。
手机壳就是装饰器,没有它手机也能正常使用,原有的功能不变,手机壳可以减轻手机滑落的损耗。

class Circle {draw() {console.log('画一个圆形')}}class Decorator {constructor(circle) {this.circle = circle}draw() {this.circle.draw()this.setRedBorder(circle)}setRedBorder(circle) {console.log('设置红色边框')}}// 测试let circle = new Circle()circle.draw()let decorator = new Decorator(cicle)decorator.draw()
// 简单的装饰器@testDec // 装饰器class Demo {}function testDec(target){target.isDec = true}console.log(Demo.isDec) // true
// 装饰器原理@decoratorclass A {}// 等同于class A {}A = decorator(A) || A; // 把A 作为参数,返回运行的结果
// 传参数function testDec(isDec) {return function(target) { // 这里要 return 一个函数target.isDec = isDec;}}@testDec(true)class Demo {// ...}alert(Demo.isDec) // true
function mixins(...list) {return function (target) {Object.assign(target.prototype, ...list)}}const Foo = {foo() {console.log('foo')}}@mixins(Foo)class MyClass{}let myclass = new MyClass()myclass.foo() // 'foo'
// 例1 只读function readonly(target, name, descriptor){// descriptor对象原来的值如下// {// value: specifiedFunction,// enumerable: false, // 可枚举// configurable: true, // 可配置// writable: true // 可写// };descriptor.writable = false;return descriptor;}class Person {constructor() {this.first = 'A'this.last = 'B'}@readonlyname() { return `${this.first} ${this.last}` }}var p = new Person()console.log(p.name())p.name = function () {} // 这里会报错,因为 name 是只读属性
// 例2 打印日志function log(target, name, descriptor) {var oldValue = descriptor.value;descriptor.value = function() {// 1. 先打印日子console.log(`Calling ${name} with`, arguments);// 2. 执行原来的代码,并返回return oldValue.apply(this, arguments);};return descriptor;}class Math {@logadd(a, b) {return a + b;}}const math = new Math();const result = math.add(2, 4);console.log('result', result);
使用者无权访问目标对象,中间加代理,通过代理做授权和控制
什么Nginx代理、JSONP、科学上网...,你平时的工作中可能都用了代理模式,只是你不知道。
模式作用:
1. 远程代理(一个对象将不同空间的对象进行局部代理)
2. 虚拟代理(根据需要创建开销很大的对象如渲染网页暂时用占位图代替真图)
3. 安全代理(控制真实对象的访问权限,经纪人一般都是暴露自己的电话,明星的电话一般情况都不会泄漏)
4. 智能指引(调用对象代理处理另外一些事情如垃圾回收机制)
UML类图

// 代理模式需要三方(买房的过程)// 1.买家function buyer() {this.name = 'FinGet'}// 2.中介function agency(){}// 卖房agency.prototype.sellhouse = function (){new seller(new buyer()).sell('100W')}// 3.卖家function seller(buyer) {this.buyerName = buyer.namethis.sell = function(money) {console.log(`收到了${this.buyerName}的${money},房子卖出`)}}
阮一峰ES6,http://es6.ruanyifeng.com/#docs/proxy
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
// 明星let star = {name: '张XX',age: 25,phone: '13910733521'}// 经纪人let agent = new Proxy(star, {get: function (target, key) {if (key === 'phone') {// 返回经纪人自己的手机号return '18611112222'}if (key === 'price') {// 明星不报价,经纪人报价return 120000}return target[key]},set: function (target, key, val) {if (key === 'customPrice') {if (val < 100000) {// 最低 10wthrow new Error('价格太低')} else {target[key] = valreturn true}}}})// 主办方console.log(agent.name)console.log(agent.age)console.log(agent.phone)console.log(agent.price)// 想自己提供报价(砍价,或者高价争抢)agent.customPrice = 150000// agent.customPrice = 90000 // 报错:价格太低console.log('customPrice', agent.customPrice)
适配器模式与代理模式:
- 适配器模式:提供一个不同的接口(如不同的插头)
- 代理模式: 提供一模一样的接口
装饰器模式与代理模式:
- 装饰器模式: 扩展功能,原有功能不变且可以直接使用
- 代理模式: 显示原有功能,但是经过限制或者是阉割之后的
为子系统中的一组接口提供了一个高层接口,使用者使用这个高层接口。外观者模式经常被开发者必备,它可以将一些复杂操作封装起来,并创建一个简单的接口用于调用。

不符合单一职责原则和开放封闭原则,因此谨慎使用,不可滥用
发布 & 订阅;一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。
模式作用:
1. 支持简单的广播通信,自动通知所有已经订阅过得对象
2. 页面载入目标对象很容易与观察者存在一种动态关联,增加了灵活性
3. 目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用
注意事项:
1. 监听要在触发之前
// 主题,接收状态变化,触发每个观察者class Subject {constructor() {this.state = 0this.observers = []}getState() {return this.state}setState(state) {this.state = statethis.notifyAllObservers()}attach(observer) {this.observers.push(observer)}notifyAllObservers() {this.observers.forEach(observer => {observer.update()})}}// 观察者,等待被触发class Observer {constructor(name, subject) {this.name = namethis.subject = subjectthis.subject.attach(this)}update() {console.log(`${this.name}:update,state:${this.subject.getState()}`)}}// 测试代码let s = new Subject()let o1 = new Observer('o1', s)let o2 = new Observer('o2', s)let o3 = new Observer('o3', s)s.setState(1)s.setState(2)s.setState(3)
<button id="btn1">btn</button>$('#btn1).click(function() {console.log(1)})// 发布订阅了一个点击事件,当点击了就会触发
let src = 'xxxxx'let result = loadImg(src)result.then(img => {console.log('g'width',img.width)return img}).then(img => {console.log('height',img.height)})
let callbacks = $.Callbacks()callbacks.add( info => {console.log('fn1',info)})callbacks.add( info => {console.log('fn2',info)})callbacks.add( info => {console.log('fn3',info)})callbacks.fire('gogogo')callbacks.fire('fire')
const EventEmitter = require('events').EventEmitterconst emitter1 = new EventEmitter()emitter1.on('some', () => {consoele.log('some event is occured 1')})emitter1.emit('some')
UML 类图

class Car {constructor(number, name) {this.number = numberthis.name = name}}class Kuaiche extends Car {constructor(number, name) {super(number, name)this.price = 1}}class Zhuanche extends Car {constructor(number, name) {super(number, name)this.price = 2}}class Trip {constructor(car) {this.car = car}start() {console.log(`行程开始,名称: ${this.car.name}, 车牌号: ${this.car.number}`)}end() {console.log('行程结束,价格: ' + (this.car.price * 5))}}let car = new Kuaiche(100, '桑塔纳')let trip = new Trip(car)trip.start()trip.end()
UML

// 停车场class Park {constructor(floors) {this.floors = floors || []this.camera = new Camera()this.screen = new Screen()this.carList = {} // 存储摄像头拍摄返回的车辆信息}in (car) {// 通过摄像头获取信息const info = this.camera.shot(car)// 停到某个车位const i = parseInt(Math.random()*100%100)const place = this.floors[0].places[i]place.in()info.place = placethis.carList[car.num] = info}out (car) {// 获取信息const info = this.carList[car.num]const place = info.placeplace.out()this.screen.show(car, info.inTime)delete this.carList[car.num]}emptyNum () {return this.floors.map(floor => {return `${floor.index} 层 还有${floor.emptyPlaceNum()}个空闲车位`}).join('\n')}}// 车class Car {constructor(num) {this.num = num}}// 层class Floor {constructor(index, places) {this.index = indexthis.places = places || []}emptyPlaceNum () {let num = 0this.places.forEach(p => {if(p.empty) {num ++}})return num}}// 车位class Place {constructor() {this.empty = true}in () {this.empty = false}out () {this.empty = true}}// 摄像头class Camera {shot (car) {return {num: car.num,inTime: Date.now()}}}// 显示屏class Screen {show (car, inTime) {console.log('车牌号'+car.num)console.log('停车时间'+ Date.now() - inTime)}}// 测试代码------------------------------// 初始化停车场const floors = []for (let i = 0; i < 3; i++) {const places = []for (let j = 0; j < 100; j++) {places[j] = new Place()}floors[i] = new Floor(i + 1, places)}const park = new Park(floors)// 初始化车辆const car1 = new Car('A1')const car2 = new Car('A2')const car3 = new Car('A3')console.log('第一辆车进入')console.log(park.emptyNum())park.in(car1)console.log('第二辆车进入')console.log(park.emptyNum())park.in(car2)console.log('第一辆车离开')park.out(car1)console.log('第二辆车离开')park.out(car2)console.log('第三辆车进入')console.log(park.emptyNum())park.in(car3)console.log('第三辆车离开')park.out(car3)