五大設(shè)計(jì)原則 (SOLID)
S 單一職責(zé)原則
一個(gè)程序只做好一件事
功能獨(dú)立拆開常侣,保持每個(gè)部分干凈獨(dú)立 (便于組合與復(fù)用)
O 開放封閉原則
對(duì)擴(kuò)展開放,對(duì)修改封閉
增加需求時(shí)弹渔,擴(kuò)展新代碼胳施,而非修改已有代碼
L 李氏置灰原則
子類能夠覆蓋父類
父類能夠出現(xiàn)的地方,子類一樣能出現(xiàn)
JS中使用較少(弱類型 & 繼承使用較少)
I 接口獨(dú)立原則
保持接口的單一獨(dú)立肢专,避免出現(xiàn)“胖接口”
JS中沒(méi)有接口(typescript)除外舞肆,使用較少
類似于單一職責(zé)原則,這里更關(guān)注接口
D 依賴倒置原則
面向接口編程博杖,依賴于抽象而不依賴于具體
使用方只關(guān)注于接口而不關(guān)注具體實(shí)現(xiàn)
JS中使用較少(沒(méi)有接口 & 弱類型)
23種設(shè)計(jì)模式
設(shè)計(jì)模式分為:創(chuàng)建型(工廠模式椿胯、單例模式、原型模式)剃根、結(jié)構(gòu)型(適配器模式|裝飾器模式|代理模式|外觀模式|橋接模式|組合模式|享元模式)哩盲、行為型(策略模式|模板方法模式|觀察者模式|迭代器模式|職責(zé)鏈模式|命令模式|備忘錄模式|狀態(tài)模式|訪問(wèn)者模式|中介者模式|解釋器模式)。
工廠模式
將new 操作進(jìn)行封裝
遇到new時(shí)狈醉,就該考慮是否使用工廠模式廉油。如:React.createElement(),源碼自行查看哈
demo代碼:
class Product {
constructor (name) {
name = name
}
init () {
console.log('init====')
}
fun1() {
console.log('fun1====')
}
fun2() {
console.log('fun2====')
}
}
class Factory {
constructor () {}
create() {
return new Product()
}
}
const factory = new Factory()
const product = factory.create()
console.log('product====', product)
product.init()
product.fun1()
product.fun2()
設(shè)計(jì)原則驗(yàn)證:
構(gòu)造函數(shù)和創(chuàng)建者分離
符合開放封閉原則
單例模式
系統(tǒng)中被唯一使用的
一個(gè)類只有一個(gè)實(shí)例
demo代碼:
class SingleObject {
constructor () {}
showCart () {
console.log('顯示購(gòu)物車')
}
}
SingleObject.getInstence = (function() {
let instance
return function() {
if (!instance) {
instance = new SingleObject()
}
return instance
}
})()
const singleObject1 = SingleObject.getInstence()
const singleObject2 = SingleObject.getInstence()
singleObject1.showCart() // log 顯示購(gòu)物車
singleObject2.showCart() // log 顯示購(gòu)物車
console.log('singleObject1===singleObject2', singleObject1===singleObject2) // true
設(shè)計(jì)原則驗(yàn)證:
符合單一職責(zé)原則舔糖,只實(shí)例化一個(gè)對(duì)象
沒(méi)法具體開放封閉原則娱两,但是不違反開放封閉原則
適配器模式
舊接口對(duì)使用者不兼容
中間加個(gè)適配器轉(zhuǎn)換接口
demo代碼
// 原代碼jquery的ajax請(qǐng)求,想要去除jquery庫(kù)金吗,這樣就導(dǎo)致$.ajax 不能被使用者使用,需要適配兼容趣竣。
// 新ajax請(qǐng)求
ajax(someoptions)
// 原項(xiàng)目中的jquery的ajax請(qǐng)求摇庙,當(dāng)去除jquery后,需要保證原來(lái)的ajax請(qǐng)求功能可用
$.ajax()
// 兼容適配:
const $ = {
ajax: function(options) {
return ajax(options) // 兼容支持
}
}
設(shè)計(jì)原則驗(yàn)證:
將舊接口與使用者分離
符合開放封閉原則
裝飾器模式
為對(duì)象添加新功能
不改變其原有結(jié)構(gòu)與功能
class Circle {
constructor () {
}
draw () {
console.log('這是一個(gè)圓形')
}
}
class Decorator {
constructor (circle) {
this.circle = circle
}
draw () {
console.log('--------------------')
this.circle.draw()
this.setRedBorder()
}
setRedBorder () {
console.log('我是添加的紅色邊框')
}
}
const circle = new Circle()
circle.draw()
const deCircle = new Decorator(circle)
deCircle.draw()
設(shè)計(jì)原則驗(yàn)證:
將現(xiàn)有對(duì)象與裝飾器進(jìn)行分離遥缕,兩者獨(dú)立存在
符合開放封閉原則
代理模式
使用者無(wú)法直接使用目標(biāo)對(duì)象
通過(guò)中間加代理來(lái)授權(quán)和控制目標(biāo)對(duì)象
例如:事件代理卫袒、
demo代碼
class RealImg {
constructor (fileName) {
this.fileName = fileName
this.loadImgFromDisk ()
}
display () {
console.log('顯示真實(shí)圖片')
}
loadImgFromDisk () {
console.log('加載本地圖片'+this.fileName)
}
}
class ProxyImg {
constructor (fileName) {
this.realImg = new RealImg(fileName)
}
display () {
console.log('我是通過(guò)代理來(lái)顯示圖片' + this.realImg.fileName)
}
}
const proxyImg = new ProxyImg('名稱1')
proxyImg.display()
設(shè)計(jì)原則驗(yàn)證:
代理類和目標(biāo)類分離,隔離開目標(biāo)類和使用者
符合開放封閉原則
外觀模式
外觀模式是一個(gè)胖接口
為子系統(tǒng)一組接口提供一個(gè)高層接口
使用者使用這個(gè)高層接口
demo代碼
function bindEvent(ele, type, selector, fn) {
if (fn == null) {
fn = selector
selector = null
}
// ....
}
// 即可以傳四個(gè)參數(shù)单匣、也可以傳三個(gè)參數(shù)
bindEvent(eleDom, 'click', '#selector', fn)
bindEvent(eleDom, 'click', fn)
設(shè)計(jì)原則驗(yàn)證:
不符合單一職責(zé)原則夕凝、開放封閉原則
謹(jǐn)慎使用宝穗,不濫用
觀察者模式
發(fā)布 & 訂閱
一對(duì)N
demo代碼:
class Subject {
constructor () {
this.state = null
this.observes = []
}
setState (state) {
this.state = state
this.notifyAllObservers()
}
getState () {
return this.state
}
attach (observer) {
this.observes.push(observer)
}
notifyAllObservers () {
this.observes.forEach(observer => {
observer.update()
})
}
}
class Observer {
constructor (name, subject) {
this.name = name
this.subject = subject
this.subject.attach(this) //初始化觀察者把自己添加到主題
}
/**
* 數(shù)據(jù)更新操作
*/
update () {
console.log(`${this.name}update, state: ${this.subject.getState()}`)
}
}
// 實(shí)例
const s = new Subject()
const ob1 = new Observer('m1', s)
const ob2 = new Observer('m2', s)
const ob3 = new Observer('m3', s)
s.setState(1)
s.setState(2)
s.setState(3)
設(shè)計(jì)原則驗(yàn)證:
主題和觀察者分離,不是主動(dòng)觸發(fā)而是被動(dòng)監(jiān)聽码秉,兩者解耦
符合開放封閉原則
迭代器模式
順序訪問(wèn)一個(gè)集合
使用者無(wú)需知道集合的內(nèi)部結(jié)構(gòu)逮矛,能遍歷訪問(wèn)每個(gè)元素
例如: jQuery each, ES6 Iterator
demo代碼:
// 迭代器
class Iterator {
constructor (container) {
this.list = container.list
this.index = 0
}
next () {
if (this.hasNext()) {
return this.list[this.index ++]
}
return null
}
hasNext () {
if (this.index >= this.list.length) {
return false
}
return true
}
}
// 使用者
class Container {
constructor (list) {
this.list = list
}
// 生成迭代器工廠
createIterator () {
return new Iterator(this) // 傳遞當(dāng)前Container 類的實(shí)例
}
}
// demo 測(cè)試
const list1 = [1,2,3,4,5,6]
const container = new Container(list1)
const iterator = container.createIterator() // 獲得遍歷器
while (iterator.hasNext()) {
console.log(iterator.next())
}
簡(jiǎn)單封裝
function each(data) {
// 生成遍歷器
let iterator = data[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
}
let arr = [1,2,3,4,5,6,7,8]
each(arr)
打印結(jié)果
設(shè)計(jì)原則驗(yàn)證:
迭代器對(duì)象和目標(biāo)對(duì)象分離
迭代器將使用者和目標(biāo)對(duì)象隔離開
符合開放封閉原則
狀態(tài)模式
一個(gè)對(duì)象有狀態(tài)變化
每次狀態(tài)變化都會(huì)觸發(fā)一個(gè)邏輯
不能都是if... else 來(lái)控制
demo代碼
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
}
}
const context = new Context()
const state1 = new State('red')
const state2 = new State('blue')
const state3 = new State('blank')
state1.handle(context) // 切換為red
state2.handle(context) // 切換為blue
state3.handle(context) // 切換為blank
結(jié)果:
設(shè)計(jì)原則驗(yàn)證:
講狀態(tài)對(duì)象與主題對(duì)象分離,狀態(tài)的變化邏輯單獨(dú)處理
符合開放封閉原則
原型模式
因重新創(chuàng)建一個(gè)對(duì)象new 個(gè)對(duì)象转砖,開銷比較大活不合適
基于原型對(duì)象(自己本身)clone 出一個(gè)對(duì)象
例如:Object.create(obj)
demo 代碼:
const prototype = {
getInfo: function() {
return `name: ${this.name}, age: ${this.age}`
},
sayInfo: function() {
console.log(this.getInfo())
}
}
const obj1 = Object.create(prototype)
obj1.name = 'meng'
obj1.age = 32
obj1.sayInfo() // name: meng, age: 32
橋接模式
抽象與實(shí)現(xiàn)分離
符合開放封閉原則
demo代碼:
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}`)
}
}
const red = new Color('red')
const circle = new Shape('circle', red)
const yellow = new Color('yellow')
const triangle = new Shape('triangle', yellow)
circle.draw()
triangle.draw()
設(shè)計(jì)原則驗(yàn)證:
抽象和實(shí)現(xiàn)分離须鼎,解耦
符合開放封閉原則
組合模式
生成樹形結(jié)構(gòu),表示“整體-部分”的關(guān)系
讓整體和部分都具有一致的操作性
例如:vnode 屬性結(jié)構(gòu)
demo代碼
const vnode = {
tag: 'div',
attr: {
id: "app",
className: "container"
},
children: [
{
tag: 'div',
attr: {
id: 'item1',
className: 'item'
},
children: ['text node']
},
{
tag: 'div',
attr: {
id: 'item1',
className: 'item'
},
children: ['text node']
},
{
tag: 'div',
attr: {
id: 'item1',
className: 'item'
},
children: ['text node']
}
]
}
設(shè)計(jì)原則驗(yàn)證:
將整體和單個(gè)節(jié)點(diǎn)操作抽象出來(lái)
符合開放封閉原則
享元模式
共享內(nèi)存府蔗,共享數(shù)據(jù)晋控,共享部分與其它業(yè)務(wù)隔離,當(dāng)需要修改或添加共享部分只修改共享塊不用每個(gè)具體邏輯去修改
符合開放封閉原則
策略模式
不同策略姓赤,分開處理
避免出現(xiàn)大量if... else 或 switch...case
demo 代碼:
class User {
constructor (type) {
this.type = type
}
// 需要改造buy
buy () {
if (this.type === 'ordinary') {
console.log('ordinary buy')
} else if (this.type === 'member') {
console.log('member buy')
} else if (this.type === 'vip') {
console.log('vip buy')
}
}
}
const ordinaryUser = new User('ordinary')
const memberUser = new User('member')
const vipUser = new User('vip')
ordinaryUser.buy()
memberUser.buy()
vipUser.buy()
console.log('改造后=====================================')
// 改造后
class OrdinaryUser {
buy () {
console.log('ordinary buy')
}
}
class MemberUser {
buy () {
console.log('member buy')
}
}
class VipUser {
buy () {
console.log('vip buy')
}
}
const ordinary = new OrdinaryUser()
const member = new MemberUser()
const vip = new VipUser()
ordinary.buy()
member.buy()
vip.buy()
模板方法模式
有幾步處理或邏輯執(zhí)行處理赡译,進(jìn)行統(tǒng)一封裝在一個(gè)地方觸發(fā)
demo代碼
class Action {
init () {
this.handle1()
this.handle2()
this.handle3()
}
handle1 () {
console.log('handle1')
}
handle2 () {
console.log('handle2')
}
handle3 () {
console.log('handle3')
}
}
const action = new Action()
action.init()
職責(zé)鏈模式
各職責(zé),鏈?zhǔn)讲僮?br>
例如: promise.then.then不铆、jquery 鏈?zhǔn)秸{(diào)用蝌焚、node 的pipe管道流
demo代碼:
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()
}
}
}
const action = new Action('組長(zhǎng)')
const jlAction = new Action('經(jīng)理')
const zjAction = new Action('總監(jiān)')
action.setNextAction(jlAction)
jlAction.setNextAction(zjAction)
action.handle()
命令模式
發(fā)布者與執(zhí)行者隔離
通過(guò)中間加入命令者對(duì)象,作為中轉(zhuǎn)站
demo代碼:
// 接受者
class Receiver {
// 執(zhí)行
exec() {
console.log('執(zhí)行')
}
}
// 命令者
class Command {
constructor (receiver) {
this.recerver = receiver
}
cmd () {
console.log('執(zhí)行命令/號(hào)令')
this.recerver.exec()
}
}
// 發(fā)布者/觸發(fā)者
class Invoker {
constructor (command) {
this.command = command
}
invoke () {
console.log('發(fā)布/觸發(fā)')
this.command.cmd()
}
}
const receiver = new Receiver()
const command = new Command(receiver)
const invoker = new Invoker(command)
invoker.invoke()
設(shè)計(jì)原則驗(yàn)證:
執(zhí)行對(duì)象與命令對(duì)象分開
符合開放封閉原則
備忘錄模式
隨時(shí)記錄一個(gè)狀態(tài)的變化
隨時(shí)可以恢復(fù)之前的某個(gè)狀態(tài)(如撤銷功能)
demo代碼:
// 狀態(tài)備忘
class Momento {
constructor (content) {
this.content = content
}
getContent () {
return this.content
}
}
// 備忘列表
class CareTaker{
constructor () {
this.list = []
}
// 添加備忘對(duì)象
add (momento) {
this.list.push(momento)
}
// 獲取備忘對(duì)象
get (index) {
return this.list[index]
}
}
// 編輯器/操作
class Editor {
constructor () {
this.content = null
}
setContent (content) {
this.content = content
}
getContent () {
return this.content
}
saveContentToMomento () {
return new Momento(this.content)
}
getContentFromMomento (momento) {
this.content = momento.getContent()
}
}
const editor = new Editor()
const careTaker = new CareTaker()
editor.setContent('aaa')
editor.setContent('bbb')
careTaker.add(editor.saveContentToMomento()) // 將當(dāng)前內(nèi)容備忘列表
editor.setContent('ccc')
careTaker.add(editor.saveContentToMomento()) // 將當(dāng)前內(nèi)容備忘列表
editor.getContentFromMomento(careTaker.get(0)) // 從備忘列表獲取第0個(gè)
console.log(editor.getContent()) // 獲得當(dāng)前內(nèi)容
editor.getContentFromMomento(careTaker.get(1)) // 從備忘錄列表獲取第1個(gè)
console.log(editor.getContent()) // 獲得當(dāng)前內(nèi)容
設(shè)計(jì)原則驗(yàn)證:
狀態(tài)對(duì)象與使用者分開狂男,解耦
符合開放封閉原則
中介者模式
通過(guò)關(guān)聯(lián)對(duì)象综看,通過(guò)中介者隔離
符合開放封閉原則
demo代碼:
class A {
constructor () {
this.number = 10
}
setNumber (num, meditor) {
this.number = num
if (meditor) {
meditor.setBNumber()
}
}
}
class B {
constructor () {
this.number = 20
}
setNumber (num, meditor) {
this.number = num
if (meditor) {
meditor.setANumber()
}
}
}
class Meditor {
constructor (a, b) {
this.a = a
this.b = b
}
setANumber () {
let number = this.a.number
this.b.setNumber(number * 10)
}
setBNumber () {
let number = this.b.number
this.a.setNumber(number/10)
}
}
const a = new A()
const b = new B()
const meditor = new Meditor(a, b)
a.setNumber(10, meditor)
b.setNumber(20, meditor)
console.log(a.number, b.number)
訪問(wèn)者模式
將數(shù)據(jù)操作與數(shù)據(jù)結(jié)構(gòu)進(jìn)行分離
使用場(chǎng)景不多
解釋器模式
描述語(yǔ)言語(yǔ)法如何定義,如何解釋和編譯
用于專業(yè)場(chǎng)景岖食,如果babel工具解釋編譯類的