设计模式,从设计到模式
- 设计:设计原则(统一指导思想)
- 模式:通过概念总结出的一些模板,可以效仿的固定式的东西(根据指导思想结合开发经验,总结出固定的样式或模板)
按类型分
创建型(对象的创建及生成)
组合型(对象和类是怎样的组合形式,一个类不一定能满足需求,通过组合的形式完成)
行为型(涵盖了开发中的一些常用的行为,如何设计才能满足需求)
工厂模式(实例化对象模式)
- demo
//Creator是个工厂,有个create函数,工厂通过create创建Product
class Product {
constructor(name) {
this.name = name
}
init() {
alert(`${this.name}`)
}
fn1() {
alert('this is fn1')
}
fn2() {
alert('this is fn2')
}
}
class Creator {
create(name) {
// 生成类的一个实例
return new Product(name)
}
}
// 测试
// 生成一个工厂
let creator = new Creator()
// 通过工厂省城product的实例
let p = creator.create('p')
p.init()
p.fn1()
let p1 = creator.create('p1')
p1.init()
// 通过工厂模式将函数封装好,暴露一个接口即可
class jQuery {
constructor(selector) {
// 获取数组的slice
let slice = Array.prototype.slice
// 获取节点,利用slice.call将其结果返回给一个数组,因为可能是多个dom节点
let dom = slice.call(document.querySelectorAll(selector))
// 获取dom的长度
let len = dom ? dom.length : 0
// 进行循环
for (let i = 0; i < len; i++) {
// 将dom的数组元素赋值给this也就是实例的元素,元素的k就是数组的k,0,1,2...
this[i] = dom[i]
}
// 赋值数组的长度
this.length = len
this.selector = selector || ''
}
append(node) {
//...
}
addClass(name) {
//...
}
html(data) {
//...
}
// 此处省略若干 API
}
// 这个函数相当于工厂,封装了返回实例的操作
// 入口,这个$是个函数,函数里面返回一个jquery实例
window.$ = function(selector) {
return new jQuery(selector)
}
console.log($(p))
题外话:读源码lib的意义
1、学习如何实现功能
2、学习设计思路
3、强制模拟好的代码
- 设计原则验证
1、构造函数和创建者分离
2、符合开放封闭原则
单例模式
介绍
- 系统中被唯一使用
- 一个类只有一个实例(在内部使用实例,不可以在外部使用)
举例
- 登录框
- 购物车
说明
- js中没有private这个私有属性关键字,typescript除外
java代码实例
js代码实例
// js中使用单例模式
class SingleObject {
// 在这里面定义的方法非静态,初始化实例时,都会有login()这个方法
login() {
console.log('login...')
}
}
// 定义一个静态的方法,将方法挂载到class上面,无论SingleObject被new多少个,getInstance的方法只有一个
SingleObject.getInstance = (function() {
let instance
return function() {
// 如果没有则赋值,初始化
if (!instance) {
instance = new SingleObject();
}
// 有的话直接返回
return instance
}
})()
// js中的单例模式只能靠文档去约束,不能使用private关键字去约束
// 测试:注意这里只能使用静态函数getInstance,不能使用new SingleObject()
let obj1 = SingleObject.getInstance()
obj1.login()
let obj2 = SingleObject.getInstance()
obj2.login()
// 单例模式(唯一的),每次获取的都是一个东西,所以他两三等,否则就不是单例模式
console.log(obj1 === obj2) //true
- jquery中的$是单例模式
// 不管用几个jquery文件,他自字内部会处理,引用同一个$
// 单例模式的思想,如果有直接用,没有的话,实例化一个
if(window.jQuery != null){
return window.jQuery
}else{
// init
}
- 模拟登录框
class LoginForm {
constructor() {
// 初始化属性,默认是隐藏的
this.state = "hide"
}
show() {
// 当前是show,alert已经显示
if (this.state == "show") {
alert('已经显示')
// return 不在执行了
return
}
// 如果不等于show,就继续执行,让他等于show
this.state = "show"
alert('登录框显示成功')
}
hide() {
if (this.state = "hide") {
alert('已经隐藏')
return
}
this.state = "hide"
alert('登录框隐藏')
}
}
// 自定义函数在这里定义的目的,就是为了加一个闭包的变量instace,防止变量污染
LoginForm.getInstance = (function() {
let instace
// 这里函数目的在于获取单例的变量
return function() {
if (!instace) {
instace = new LoginForm()
}
return instace
}
})()
// 测试
// 模拟第一个页面的登录框让其显示
let login1 = LoginForm.getInstance()
login1.show()
// 模拟第二个页面的登录框让其隐藏
let login2 = LoginForm.getInstance()
login2.show()
- vue中的store也是单例模式
- 设计原则验证
1、符合单一职责原则,只实例化唯一的对象
2、没法具体体现开放封闭原则,但是绝不违反开放封闭原则
适配器模式
介绍
- 旧接口格式和使用者不兼容
- 中间加一个适配转换接口
js代码演示
// 旧接口
class Adaptee{
specificRequest(){
return '这是旧接口'
}
}
// 新接口
class Target{
constructor(){
this.adaptee = new Adaptee()
}
request(){
// 进行转换
let info = this.adaptee.specificRequest()
return `${info}--新接口`
}
}
// 测试
let target = new Target()
console.log(target.request())
-
封装旧接口(旧接口和新接口起冲突时,使用适配器修改)
- vue中的computed就是适配模式
- 设计原则验证
1、将旧接口和使用者进行分离(所有分离和解耦的方式都属于开放封闭原则)
2、符合开放封闭原则
装饰器模式(既能使用原有的功能,又能使用装饰后的功能)
介绍
- 为对象添加新功能
- 不改变其原有的结构和功能
举例
- 比如手机可以打电话、发短信,我们在原有的基础上装个保护壳,防止摔落时损坏
js代码演示
class Circle {
draw() {
console.log('我要画一个圆')
}
}
class Decorator {
constructor(circle) {
this.circle = circle
}
draw() {
this.circle.draw()
this.setBorder(circle)
}
setBorder(circle) {
console.log('我还画了条线')
}
}
// 测试
// 想引用某个类的时候将他实例化,以参数的形式进行传递进去
let circle = new Circle()
circle.draw()
console.log('+++++')
let dec = new Decorator(circle)
dec.draw()
ES7装饰器
考虑到浏览器兼容问题,需要安装插件支持
1、npm install babel-plugin-transform-decorators-legacy --save-dev
2、对babel进行配置
- 装饰类
// 1、首先定义一个类
// 2、定义一个函数,传个target参数
// 3、在Demo这个类上面,加个@testDemo(这个就是装饰器),通过@语法将这个类装饰一遍,target这个参数其实就是Demo这个类
@testDemo
class Demo{
}
function testDemo(target){
target.isDec = true
}
alert(Demo.isDec)
// +++++++++++++++++++++++++++++++
// 装饰器原理
/*@decorator
class A {}*/
// 等同于
class A {}
// 将A定义成decorator函数执行一遍的返回值(相当于A在decorator执行了一遍),没有的话返回A
A = decorator(A) || A
// +++++++++++++++++++++++++++++++
// 可以加个参数
@testDemo1(false)
class Demo1{
}
function testDemo1(isDec){
// 这里面返回一个函数,装饰器返回的都是一个函数
return function(target){
target.isDec = isDec
}
}
alert(Demo1.isDec)
- 装饰类—mixinshi示例
// 先定义一个mixins,获取解构的集合...list
function mixins(...list) {
// return一个函数,装饰器都是一个函数
return function (target) {
// 将target.prototype和...list集合 混合在一起
Object.assign(target.prototype, ...list)
}
}
// 混合的对象
const Foo = {
foo() { alert('foo') }
}
// 加个mixins的装饰器,将Foo对象传递进去
// @mixins(Foo) 一执行,最后返回的是个函数,通过@关键字使用装饰器,将Foo里面的属性和并到target.prototype上
@mixins(Foo)
class MyClass {}
// MyClass()里面没有Foo,通过装饰器将他们合并便具有了foo()方法
let obj = new MyClass();
obj.foo() // 'foo'
- 装饰方法—示例1
// 所有装饰器都是一个函数,最后将其返回
/*
@param target——Person这个类
@param name——当前属性点
@param descriptor——属性描述
*/
function readonly(target, name, descriptor){
// descriptor对象原来的值如下
// {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// };
// 将可写的关闭
descriptor.writable = false;
// 将其返回,所有被@readonly装饰过的只可读不可写
return descriptor;
}
class Person {
constructor() {
this.first = 'A'
this.last = 'B'
}
@readonly
// name()是Person里面的一个方法,我们想要只能获取不能修改,所以加个@readonly装饰器
name() { return `${this.first} ${this.last}` }
}
var p = new Person()
console.log(p.name())
p.name = function () {} // 这里会报错,因为 name 加了装饰器,就是只读属性
- 装饰方法—示例2
function log(target, name, descriptor) {
// 获取value,其实就是add函数
var oldValue = descriptor.value;
// 将value重新赋值一个函数
descriptor.value = function() {
console.log(`Calling ${name} with`, arguments);
// 将原本的函数执行一下也就是add(),apply改变this的指向
return oldValue.apply(this, arguments);
};
return descriptor;
}
// 定义一个Math类,希望在执行add执行的时候打印一个日志
class Math {
// 被装饰过的add()就是一个行的value,新的value就是先打印日志,再去做之前加法的执行,最后将其返回
@log
add(a, b) {
return a + b;
}
}
const math = new Math();
// 执行 add 时,会自动打印日志,因为有@log装饰器
const result = math.add(2, 4);
console.log('result', result);
- 第三方库core-decorators(提供常用的装饰器)
1、安装npm i core-decorators --save
2、直接引用,就不需要自己写了
import {readonly} from "core-decorators"
class Person {
@readonly
name(){
return 'this is test'
}
}
let p = new Person()
// 可读不可写
alert(p.name())
// 此装饰器提醒用户方法已废弃
import { deprecate } from 'core-decorators';
class Person {
@deprecate('这个里面可以自己添加提示语')
facepalm() {}
@deprecate('We stopped facepalming')
facepalmHard() {}
@deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
facepalmHarder() {}
}
let person = new Person();
person.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.
person.facepalmHard();
// DEPRECATION Person#facepalmHard: We stopped facepalming
person.facepalmHarder();
// DEPRECATION Person#facepalmHarder: We stopped facepalming
//
// See http://knowyourmeme.com/memes/facepalm for more details.
- 设计原则验证
1、将现有对象和装饰器进行分离,两者独立存在(解耦)
2、符合开放封闭原则
代理模式
介绍
- 使用者无权访问目标对象
- 中间加代理,通过代理做授权和控制
js代码演示
class ReadImg{
constructor(filename){
this.filename = filename
this.loadImg()
}
display(){
console.log('display---'+ this.filename)
}
loadImg(){
console.log('loading---'+this.filename)
}
}
class ProxyImg{
constructor(filename){
this.readImg = new ReadImg(filename)
}
display(){
this.readImg.display()
}
}
let proxyImg = new ProxyImg('1.png')
proxyImg.display()
使用场景
1、网页事件代理
- 1
- 2
- 3
- 4
- 5
2、this的指向
- 1
- 2
- 3
- 4
- 5
3、$.proxy
4、ES6 Proxy
// 明星
let star = {
name: '张XX',
age: 25,
phone: '13910733521'
}
// 经纪人agent
// star 代理的对象,监听代理的获取 get 和设置 set 属性
// 注意代理的接口要和原生的一样 比如要知道name,就写name
let agent = new Proxy(star, {
// target代理对象, key就是代理对象的值
get: function (target, key) {
if (key === 'phone') {
// 返回经纪人自己的手机号
return '18611112222'
}
if (key === 'price') {
// 明星不报价,经纪人报价
return 120000
}
// 如果不是在这个两种情况,直接返回target[key]
return target[key]
},
set: function (target, key, val) {
// 这是我们自己定义的价格
if (key === 'customPrice') {
if (val < 100000) {
// 最低 10w,小于10万,报错
throw new Error('价格太低')
} else {
target[key] = val
// 这里写renturn true 要不然不会赋值成功
return 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、符合开放封闭原则
代理模式、适配模式、装饰器模式三者的区别
- 适配器:提供一个不同的接口(进行格式的转换)
- 代理模式:提供一模一样的接口(无权使用主类,所以进行代理,提供一个一模一样的接口)
- 装饰器模式:扩展功能,原有功能不变且可直接使用
- 代理模式:显示原有功能,但是经过限制或阉割之后的
外观模式
介绍
- 为子系统中的一组接口提供了一个高层接口
-
使用者使用了这个高层接口
场景
传多个参数都可适用
设计原则验证
1、不符合单一职责和开放封闭原则,因此谨慎使用,不可滥用(不要为了设计而设计,而是为了使用而设计)
观察者模式★
介绍
- 发布 & 订阅(定好东西,付了款,会有人上门送,比如订牛奶、报纸啊等)
- 一对多(N)(可以同时订购牛奶,报纸,两者之间没什么冲突)
js代码
// 主题,保存状态,接收状态变化,状态变化后触发所有观察者对象
class Subject {
constructor() {
// 状态
this.state = 0
// 所有观察者为一个数组
this.observers = []
}
getState() {
return this.state
}
setState(state) {
this.state = state
this.notifyAllObservers()
}
// 添加一个新的观察者
attach(observer) {
this.observers.push(observer)
}
// 循环所有的观察者
notifyAllObservers() {
this.observers.forEach(observer => {
// 遍历的每个元素执行update方法
observer.update()
})
}
}
// 观察者,等待被触发
class Observer {
constructor(name, subject) {
this.name = name
this.subject = subject
// 将自己添加进去,把观察者添加到主题当中
this.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)
场景
1、网页事件绑定
所有的事件监听都是观察者模式,所有事件写好(订阅)之后,等待被执行
2、Promise中的then
在then里面写好逻辑之后,不会马上触发,等待Promise的状态发生改变时触发
3、jquery中的callbacks
这是个底层的api,比如使用ajax时,就会到这个api里面执行
4、node.js自定义事件
- event
// 基础api的引用
const EventEmitter = require('events').EventEmitter
const emitter1 = new EventEmitter()
emitter1.on('some', info => {
// 监听 some 事件
console.log('f1',info)
})
emitter1.on('some', info => {
// 监听 some 事件
console.log('f2',info)
})
// 触发 some 事件
emitter1.emit('some','XXX')
// +++++++++++++++++++++++++++++++++++
const emitter = new EventEmitter()
emitter.on('sbowName', name => {
console.log('event occured ', name)
})
// emit 时候可以传递参数过去
emitter.emit('sbowName', 'zhangsan')
// +++++++++++++++++++++++++++++++++++
// 继承
// 任何构造函数都可以继承 EventEmitter 的方法 on emit
class Dog extends EventEmitter {
constructor(name) {
super()
this.name = name
}
}
var simon = new Dog('simon')
// 因为 simon 继承了 Dog ,所以便有他的 on 方法
simon.on('bark', function () {
console.log(this.name, ' barked')
})
setInterval(() => {
// 每一秒触发bark,都回去执行 console.log(this.name, ' barked')
simon.emit('bark')
}, 500)
- stream
// stream 用到自定义事件
const fs = require('fs')
// 读取文件的 Stream
const readStream = fs.createReadStream('./data/file1.txt')
// 此方法是看文件有多少字符
let length = 0
// 监听这个流
// 将数据处理的函数和结束的函数定义好,直接去执行流,等待触发即可
readStream.on('data', function (chunk) {
// chunk读一点,吐出一点
length += chunk.toString().length
})
readStream.on('end', function () {
console.log(length)
})
- readline
var fs = require('fs')
var readline = require('readline');
// 查看有多少行
var rl = readline.createInterface({
input: fs.createReadStream('./data/file1.txt')
});
var lineNum = 0
// 这里是监听一行一行的数据
rl.on('line', function(line){
lineNum++
});
rl.on('close', function() {
console.log('lineNum', lineNum)
});
- 处理http请求
var http = require('http')
function serverCallback(req, res) {
var method = req.method.toLowerCase() // 获取请求的方法
if (method === 'get') {
}
if (method === 'post') {
// 接收 post 请求的内容
var data = ''
req.on('data', function (chunk) {
// “一点一点”接收内容
console.log('chunk', chunk.toString())
data += chunk.toString()
})
req.on('end', function () {
// 接收完毕,将内容输出
console.log('end')
res.writeHead(200, {'Content-type': 'text/html'})
res.write(data)
res.end()
})
}
}
http.createServer(serverCallback).listen(8081) // 注意端口别和其他 server 的冲突
console.log('监听 8081 端口……')
- vue和react组件生命周期触发
组件其实都是构造函数,生成一个组件相当于构造函数初始化实例 -
vue watch
设计原则验证
- 主题和观察者分离,不是主动触发而是被动监听,两者解耦
- 符合开放封闭原则
迭代器模式
介绍
1、顺序访问一个集合(有序列表,数组,对象是个无序列表)
2、使用者无需知道集合的内部结构(封装,目的在于生成一个访问机制,不需要外界知道内部结构)
示例
-
三个不同的数据结构遍历的方法有三种
-
统一的遍历方式
代码演示
class Iterator {
constructor(conatiner) {
this.list = conatiner.list
this.index = 0
}
next() {
if (this.hasNext()) {
// 如果还有下一项,直接返回当前这一项的index++
return this.list[this.index++]
}
// 如果没有,则返回null
return null
}
hasNext() {
// 判断有没有下一项
// this.index >= this.list.length 这句话的意思是有没有到头
if (this.index >= this.list.length) {
return false
}
// 如果没有就是还有下一项
return true
}
}
class Container {
constructor(list) {
this.list = list
}
// 生成遍历器
getIterator() {
// 遍历器是有依据的,所以要传递一个参数
return new Iterator(this)
}
}
// 测试代码
let container = new Container([1, 2, 3, 4, 5])
// 生成一个遍历器,通过这个遍历器可以兼容所有的有序结集合的数据结构
let iterator = container.getIterator()
while(iterator.hasNext()) {
console.log(iterator.next())
}
ES6 Iterator
-
ES6 Iterator为何存在
-
ES6 Iterator是什么
- 示例
// 传入的data可以是任意的
function each(data) {
// 生成遍历器,类似jquery生成的遍历器
let iterator = data[Symbol.iterator]()
// 有数据时返回 {value: 1, done: false}
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())
// 没有值的时候返回undefined,done 等于 true 就结束了
// console.log(iterator.next()) // 没有数据时返回 {value: undefined, done: true}
// 因为不知道所遍历数据的长度length,所以使用while循环
let item = { done: false }
while (!item.done) {
// 每次获取next
item = iterator.next()
// 判断done是否结束
if (!item.done) {
console.log(item.value)
}
}
}
// 测试
let arr = [1, 2, 3, 4]
let nodeList = document.getElementsByTagName('p')
// 如果是对象,可以利用Map.set
let m = new Map()
m.set('a', 100)
m.set('b', 200)
each(arr)
each(nodeList)
each(m)
设计原则验证
- 迭代器对象和目标对象分离
- 迭代器将使用者与目标对象隔离开
- 符合开放封闭原则
状态模式
介绍
1、一个对象有状态变化
2、每次状态变化都会触发一个逻辑
3、不能总是用if...else来控制
js代码
// 把状态抽象出来
// 状态(红绿灯)
class State {
constructor(color) {
this.color = color
}
handle(context) {
console.log(`turn to ${this.color} light`)
// 设置状态
context.setState(this)
}
}
// 主体 实例
class Context {
constructor() {
this.state = null
}
// 获取状态
getState() {
return this.state
}
setState(state) {
this.state = state
}
}
// 测试代码
let context = new Context()
let greed = new State('greed')
let yellow = new State('yellow')
let red = new State('red')
// 绿灯亮了
greed.handle(context)
console.log(context.getState())
// 黄灯亮了
yellow.handle(context)
console.log(context.getState())
// 红灯亮了
red.handle(context)
console.log(context.getState())
场景
1、有限状态机
- 有限个状态、以及在这些状态之间的变化
- 第三方库,npm i javascript-state-machine --save
Document
有限状态机
2、实现简单的Promise
- promise是个class
- 在初始化的时候传进去一个函数,函数有2个参数resolve、reject
-
要实现then的方法,then接收2个参数成功和失败
Document
设计原则验证
- 将状态对象和主题对象分离,状态的变化逻辑单独处理
- 符合开放封闭原则
设计模式文档
http://www.runoob.com/design-pattern/singleton-pattern.html