设计模式存在的根本原因是为了代码的复用,增加可维护性。多久前看过张容铭的《JavaScript设计模式》然后感觉就是设计模式在实际项目中用到的不多,除非写功能框架类的东西。最近偶然看到一篇设计模式文章,对比了下现在在做的项目的某些功能模块都对应上了,感觉啪啪的打脸。故重新补了下知识点,以最简单的例子进行总结。要求自己对于常用的设计模式,强制模仿,掌握。而非常用的设计模式,视业务场景选择性使用。
常用设计模式
创建型模式:工厂模式,单例模式。
结构性模式:适配器模式,装饰器模式,代理模式,外观模式。
行为型模式:观察者模式,迭代器模式,状态模式。
不常用设计模式
创建型模式:原型模式。
结构性模式:桥接模式,组合模式,享元模式。
行为型模式:职责链模式,命令模式,备忘录模式,中介者模式,访问者模式,解释器模式。
设计模式遵循7大原则:
1,开闭原则:对扩展开放,对修改关闭。
2,里氏转换原则: 子类继承父类,单独掉完全可以运行。
3,依赖倒转原则: 引用一个对象,如果这个对象有底层类型,直接引用底层。
4,接口隔离原则:每个接口应该是一个角色。
5,合成聚合复用原则:新的对象应该使用一些已有的对象,使之成为新对象的一部分。
6,迪米特原则:一个对象应对其他对象有尽可能少得了解。
7,单一职责原则:一个类负责一件职责 其逻辑肯定比负责多项职责简单的多。
工厂模式
定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型。
ps:商店要封装好画矩形和画圆的工作,工厂对他们进行生产,我们只管对工厂要矩形还是圆。
// 产品矩形框
class Rect {
constructor(props){
this.props = props
this.render()
}
render() {
console.log('绘画矩形')
}
}
// 产品圆框
class Arc{
constructor(){
this.render()
}
render() {
console.log('绘画矩形')
}
}
// 定义一个生产框的工厂
function frame(name) {
switch(name){
case 'rect':
return new Rect()
case 'arc':
return new Arc()
}
}
frame('rect')
jQuery例子:
class Jquery {
constructor(selector) {
// ...逻辑
}
// ...逻辑
}
window.$ = function(selector) {
return new Jquery(selector)
}
React.createElement例子:
class Vnode {
constructor(tag, attrs, children){
// ...
}
// ...
}
React.createElement = function(tag, attrs, children) {
return new Vnode(tag, attrs, children)
}
let profile = React.createElement("div", null, React.createElement("img",{src:'图片.png'},))
单例模式
单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。
ps:登录框,无论点击多少次,弹窗只应该被创建一次。
class LoginForm {
constructor(){
this.state = 'hide'
}
show() {
if (this.state === 'show') {
console.log('已经显示')
return
}
this.state = 'show'
console.log('已经显示')
}
hide() {
if (this.state === 'hide') {
console.log('已经隐藏')
return
}
this.state = 'hide'
console.log('已经隐藏')
}
}
// 确保只实例一次
let getInstance = (function(){
let instance
return function() {
if (!instance) {
instance = new LoginForm()
}
return instance
}
})()
let obj1 = getInstance()
let obj2 = getInstance()
console.log(obj1 === obj2)
jQuery里面只有一个$
if (window.jQuery != null) {
return window.jQuery
} else {
// 初始化...
}
适配器模式
将一个类(对象)的接口(方法或属性)转化成客户希望的另外一个接口(方法或属性),适配器模式使得原本由于接口不兼容而不能一起工作的那些类(对象)可以一些工作。
ps: 想使用功能,但是原有的功能不能用了。封装旧接口处理。
ajax({
url:'/getData',
type: 'Get',
dataType: 'json',
data: {
id: "123"
}
}).then((res)=>{})
// 由于历史原因,以前代码中全都是用$.ajax({...})方法
// 做一层适配器
var $ = {
ajax: (option) => {
return ajax(option)
}
}
又如vue computer
装饰器模式
提供比继承更有弹性的替代方案。装饰者用于包装同接口的对象,不仅允许你向方法添加行为,而且还可以将方法设置成原始对象调用。
ps:想使用功能,但是不满足现有的功能,给扩展。加手机壳,外壳不影响手机原有的功能(如拍照)的使用。
class Circle {
draw() {
console.log('画一个圆形')
}
}
class Decorator {
constructor(circle) {
this.circle = circle
}
draw() {
this.circle.draw()
this.setBorder(this.circle)
}
setBorder(circle) {
//...处理circle
console.log('把圆的边设成红色')
}
}
let circle = new Circle()
let dec = new Decorator(circle)
dec.draw()
又如ES7 装饰器
function decorator(isDec) {
return function(target){
target.isDec = isDec
}
}
@decorator(true)
class Demo {
// ...
}
console.log(Demo.isDec)
代理模式
为其他对象提供一种代理以控制对这个对象的访问
ps:想使用功能,但是无权使用,让别人来帮做。A想送花给B,但是又不敢去,委托C去送。
class B{
constructor(name) {
this.name = name
}
}
class A {
constructor(b) {
this.b = b
}
sendGift(gift) {
console.log(`给${this.b.name}送${gift}`)
}
}
class C {
constructor(b) {
this.b = b
}
sendGift(gift) {
let a = new A(this.b)
a.sendGift('花')
}
}
let girl = new B('小姐姐')
let proxy = new C(girl)
proxy.sendGift()
又如,ES6 Proxy,网页的事件代理。
外观模式
为子系统中的一组接口提供了一个一致的界面,此模块定义了一个高层接口,这个接口值得这一子系统更加容易使用。
function bindEvent(elem, type, selector, fn) {
if (!fn) {
fn = selector
selector = null
}
}
bindEvent(elem, 'click', '#div', fn)
bindEvent(elem, 'click', fn)
观察者模式
又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。
ps:班长要去通知班里的某些人一些事情,与其一个一个的手动调用触发的方法(私下里一个一个通知),不如维护一个列表(建一个群),这个列表存有你想要调用的对象方法(想要通知的人);之后每次通知事件的时候只要循环执行这个列表就好了(群发),而不用关心这个列表里有谁。
// 被观察者(发布者)
class Subject {
constructor(){
this.state = 0
this.observers = [] // 需要更新的列表
}
getState(){
return this.state
}
setState(state){
this.state = state
this.notifyAllObservers()
}
notifyAllObservers(){
this.observers.forEach(observer => {
observer.update()
})
}
attach(observer){
this.observers.push(observer)
}
}
// 观察者(订阅者)
class Observer {
constructor(name, subject) {
this.name = name
this.subject = subject
this.subject.attach(this)
}
update() {
console.log(`${this.name} updatae`)
}
}
let s = new Subject()
let o1 = new Observer('o1', s)
s.setState(1)
其他场景如:vue和react组件的生命周期触发,vue watch,网页事件绑定,node自定义事件。
迭代器模式(Iterator)
提供一种方法顺序一个聚合对象中各个元素,而又不暴露该对象内部表示。
ps: ES6 Iterator
/*
<div>
<p>1</p>
<p>2</p>
<p>3</p>
</div>
*/
// ES6 Iterator 示例
let arr = [1, 2, 3, 4]
let nodeList = document.getElementsByTagName('p')
let m = new Map()
m.set('a', 100)
m.set('b', 200)
// 显然三个遍历不能以一种方式变遍历,我们使用迭代器Iterator
function each(data){
// 生成遍历器
let iterator = data[Symbol.iterator]
// iterator.next() 返回有数据时{value: 1, done: false},无数据时{value: undefine, done: true}
let item = {done: false}
while (!item.done){
item = iterator.next()
if (!item.done) {
console.log(item.value)
}
}
}
each(arr)
eact(nodeList)
eact(m)
// Symbol.iterator并不是大家都知道的,并且还要去封装each方法。
// 因此有了iterator的语法糖 for...of,对上面each方法修改。
function each(data) {
for(let item of data) {
console.log(item)
}
}
状态模式
允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类。它主要有两个角色组成:
1.环境类:拥有一个状态成员,可以修改其状态并作出相应反应。
2.状态类:表示一种状态,包含其相应的处理方法。
ps:一个对象有状态变化,状态多的时候,每次状态变化都会触发一个逻辑,不能总是if...else来控制。每个状态都只可以做当前状态才可以做的事情。如交通信号灯不同颜色的变化,红绿黄之间的变化 。
// 状态类 传入红灯,绿,黄
class State {
constructor(color) {
this.color = color
}
handle(context) {
context.setState(this)
}
}
// 环境类
class Context {
constructor() {
this.state = null
}
getState() {
return this.state
}
setState(state) {
this.state = state
//....
}
}
let context = new Context()
// 亮绿灯
let lightGreen = new State('green')
lightGreen.handle(context) // 修改其状态并作出相应反应
console.log(content.getState())
// ...
其他场景如:javascript-state-machine状态机的使用,promise的pending,fullfilled,rejected。
原型模式
原型模式(prototype)是指用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象。
ps:clone自己,生成一个新对象。
Object.create的应用
let prototype = {
getName: function() {
console.log(this.first, this.last)
}
}
let x = Object.create(prototype, {
id: {
value: '111',
enumerable: true
}
})
x.first = 'A'
x.last = 'B'
x.getName()
let y = Object.create(prototype)
y.first = 'C'
y.last = 'D'
y.getName()
桥接模式
桥接模式(Bridge)将抽象部分与它的实现部分分离,使它们都可以独立地变化。
ps:用于把抽象化和实现化解耦
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)将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。
ps:生成树形结构,表示整体-部分关系,让整体和部分都具有一致的操作。
// 文件夹-整体
class Folder {
constructor(folder) {
this.folder = folder
this.lists = []
}
add(file) {
this.lists.push(file)
}
scan() {
console.log('文件夹:', this.folder)
for(let i = 0, l = this.lists.length; i < l; i++) {
this.lists[i].scan()
}
}
}
// 文件-部分
class File {
constructor(file) {
this.file = file
}
add(){
console.log('文件不能添加子文件')
}
scan(){
console.log('件夹:', this.file)
}
}
let folder = new Folder('根文件夹')
let folder1 = new Folder('JS')
let folder2 = new Folder('story')
let file = new File('深入React技术栈.pdf')
let file2 = new File('前端架构从入门到微前端')
let file3 = new File('安徒生童话故事')
folder1.add(file)
folder1.add(file2)
folder2.add(file3)
folder.add(folder1)
folder.add(folder2)
folder.scan()
console.log(folder)
策略模式
策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户。
ps:不同策略分开处理,避免出现大量if...else或switch...case。
class User {
constructor(type){
this.type = type
}
buy(){
switch (this.type) {
case 'ordinary':
console.log('普通用户购买')
break;
case 'member':
console.log('会员用户购买')
break;
case 'vip':
console.log('vip用户购买')
break;
default:
break;
}
}
}
let m1 = new User('ordinary')
m1.buy()
let m2 = new User('member')
m2.buy()
let m3 = new User('vip')
m3.buy()
// 使用策略模式修改
class Orfinary{
buy(){
console.log('普通用户购买')
}
}
class Member{
buy(){
console.log('会员用户购买')
}
}
class Vipuser{
buy(){
console.log('vip用户购买')
}
}
let m1 = new Orfinary()
m1.buy()
let m2 = new Member()
m2.buy()
let m3 = new Vipuser()
m3.buy()
职责链模式
职责链模式是使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。
ps:链中收到请求的对象要么亲自处理它,要么转发给链中的下一个候选者。
class Actoin{
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 Actoin('组长')
let a2 = new Actoin('经理')
let a3 = new Actoin('总监')
a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()
又如, js中的链式操作,promise.then的链式操作。
命令模式
命令模式的定义是:用于将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及执行可撤销的操作。也就是说改模式旨在将函数的调用、请求和操作封装成一个单一的对象,然后对这个对象进行一系列的处理。此外,可以通过调用实现具体函数的对象来解耦命令对象与接收对象。
ps:执行命令时,发布者和接受者分开。
// 执行者
class Receiver {
exec(){
console.log('执行')
}
}
// 命令对象
class Command {
constructor(receiver) {
this.receiver = receiver
}
cmd() {
console.log('触发命令')
this.receiver.exec()
}
}
// 发布者
class Invoker {
constructor(command) {
this.command = command
}
invoke() {
console.log('开始')
this.command.cmd()
}
}
// 士兵
let soldier = new Receiver()
// 小号手
let trumpeter = new Command(soldier)
// 将军
let general = new Invoker(trumpeter)
general.invoke()
备忘录模式
撤销功能我们不由得想起备忘录模式。在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。
// 状态备忘录
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(memento)
}
}
let editor = new Editor()
let careTaker = new CareTaker()
// 编辑器设置一条数据
editor.setContent('111')
// 存储备忘录
careTaker.add(editor.saveContentToMemento()) // 存进去的是Memento的实例
// 编辑器设置第二条数据
editor.setContent('222')
// 存储备忘录
careTaker.add(editor.saveContentToMemento())
// 撤销操作
editor.getContentFromMemento(careTaker.get(0))
中介者模式
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
ps:买房租房中的中介
class Mediator {
constructor(a, b) {
this.a = a
this.b = b
}
setA() {
let number = this.b.number
this.a.setNumber(number*100)
}
setB() {
let number = this.a.number
this.b.setNumber(number/100)
}
}
class A {
constructor(){
this.number = 0
}
setNumber(num, m) {
this.number = num
if (m) {
m.setB()
}
}
}
class B {
constructor(){
this.number = 0
}
setNumber(num, m) {
this.number = num
if (m) {
m.setA()
}
}
}
let a = new A()
let b = new B()
let m = new Mediator(a, b)
a.setNumber(100, m)
console.log(a.number, b.number
b.setNumber(100, m)
console.log(a.number, b.number)
又如,网页富文本编辑器操作,浏览器封装了一个命令对象。
参考链接:
[汤姆大叔的博客]