時間:2023-10-07 18:18:01 | 來源:網(wǎng)站運營
時間:2023-10-07 18:18:01 來源:網(wǎng)站運營
前端進階之路:1.5w字整理23種前端設計模式:我們開發(fā)人員經(jīng)常會說:"Talk is cheap, show me the code"。要想寫出令人賞心悅目的代碼,我覺得是否使用了合理的設計模式起了至關重要的作用。class GetUser { constructor(id) { this.id = id } getInfo() { const params = {id: this.id} //...code here }}class GetVipUser extends GetUser { constructor(id, vipLevel) { super(id) this.id = id this.level = vipLevel } getInfo() { const params = {id: this.id} if (this.level != void 0) { params.level = this.level } super.getInfo(params) }}class Demo { getUser(user) { console.log(user.getInfo()) }}// 里式替換原則const u = new Demo()u.getUser(new GetUser())u.getUser(new GetVipUser())
我們看到GetVipUser的設計是符合里式替換原則的,其可以替換父類出現(xiàn)的任何位置,并且原來代碼的邏輯行為不變且正確性也沒有被破壞。class GetUser { constructor(id) { this.id = id } getInfo() { const params = {id: this.id} //...code here }}class GetVipUser extends GetUser { constructor(id, vipLevel) { super(id) this.id = id this.level = vipLevel } getInfo() { const params = {id: this.id} if (this.level == void 0) { throw new Error('level should not undefind') } super.getInfo(params) }}class Demo { getUser(user) { console.log(user.getInfo()) }}// 里式替換原則const u = new Demo()u.getUser(new GetUser())u.getUser(new GetVipUser())
改動之后我們可以很清晰的看到,父類在運行時是不會出錯的,但是子類當沒有接受level的時候回拋出錯誤,整個程序的邏輯和父類產(chǎn)生了區(qū)別,所以是不符合里式替換原則的。//方法一class GetSeetingConfig { static instance = null constructor() { console.log('new') } getConfig() { //... } static getInstance () { if (this.instance == void 0) { this.instance = new GetSeetingConfig() } return this.instance }}const seeting1 = GetSeetingConfig.getInstance()const seeting2 = GetSeetingConfig.getInstance()//兩次只打印一次newseeting1 === seeting2 // true//方法二class GetSeetingConfig { constructor() { console.log('new') } getConfig() { //... }}GetSeetingConfig.getInstance = (function() { let instance return function() { if (!instance){ instance = new GetSeetingConfig() } return instance }})()const seeting1 = GetSeetingConfig.getInstance()const seeting2 = GetSeetingConfig.getInstance()//兩次只打印一次newseeting1 === seeting2 // true
優(yōu)點://簡單工廠模式class User { constructor(role, name) { this.name = name; this.role = role }}class Admin { constructor(role, name) { this.name = name; this.role = role }}class SuperAdmin { constructor(role, name) { this.name = name; this.role = role }}class RoleFactory { static createUser(role) { if (role === 'user') { return new User(role,'用戶') } else if (role === 'admin') { return new Admin(role, '管理員') } else if (role === 'superadmin') { return new SuperAdmin(role, '超級管理員') } }}const user = RoleFactory.createUser('user'')
簡單工廠的優(yōu)點在于,你只需要一個正確的參數(shù),就可以獲取到你所需要的對象,而無需知道其創(chuàng)建的具體細節(jié)。但是當內(nèi)部邏輯變得很復雜這個函數(shù)將會變得很龐大并且難以維護。class UserFactory { constructor(role, name) { this.name = name; this.role = role; } init() { //我們可以把簡單工廠中復雜的代碼都拆分到每個具體的類中 // code here //... return new User(this.role, this.name) }}class AdminFactory { constructor(role, name) { this.name = name; this.role = role; } init() { //我們可以把簡單工廠中復雜的代碼都拆分到每個具體的類中 // code here //... return new Admin(this.role, this.name) }}class SuperAdminFactory { constructor(role, name) { this.name = name; this.role = role; } init() { //我們可以把簡單工廠中復雜的代碼都拆分到每個具體的類中 // code here //... return new SuperAdmin(this.role, this.name) }}class RoleFactory { static createUser(role) { if (role === 'user') { return new UserFactory(role,'用戶') } else if (role === 'admin') { return new AdminFactory(role, '管理員') } else if (role === 'superadmin') { return new SuperAdminFactory(role, '超級管理員') } }}const user = RoleFactory.createUser('user'')
那什么時候該用工廠方法模式,而非簡單工廠模式呢?class Factory { createUserParser(){ thorw new Error('抽象類只能繼承,不能實現(xiàn)') } createLoginParser(){ thorw new Error('抽象類只能繼承,不能實現(xiàn)') }}class UserParser extends Factory { createUserParser(role, name) { return new UserFactory(role, name) } createLoginParser(type) { if (type === 'email'){ return new UserEmail() } else if (type === 'phone') { return new UserPhone() } }}class AdminParser extends Factory { createUserParser(role, name) { return new AdminFactory(role, name) } createLoginParser(type) { if (type === 'email'){ return new AdminEmail() } else if (type === 'phone') { return new AdminPhone() } }}class SuperAdminParser extends Factory { createUserParser(role, name) { return new SuperAdminFactory(role, name) } createLoginParser(type) { if (type === 'email'){ return new SuperAdminEmail() } else if (type === 'phone') { return new SuperAdminPhone() } }}
總結class Cake { constructor(name, color, shape, suger) { this.name = name; this.color = color; this.shape = shape; this.suger = suger; }}new Cake('cake', 'white', 'circle', '30%')
現(xiàn)在,Cake 只有 4 個可配置項,對應到構造函數(shù)中,也只有 4 個參數(shù),參數(shù)的個數(shù)不多。但是,如果可配置項逐漸增多,變成了 8 個、10 個,甚至更多,那繼續(xù)沿用現(xiàn)在的設計思路,構造函數(shù)的參數(shù)列表會變得很長,代碼在可讀性和易用性上都會變差。在使用構造函數(shù)的時候,我們就容易搞錯各參數(shù)的順序,傳遞進錯誤的參數(shù)值,導致非常隱蔽的 bug。class Cake { consotructor(name, color) { this.name = name; this.color = color; } validName() { if(this.name == void 0) { console.log('name should not empty') return false } return true } validColor() { if (this.color == void 0) { console.log('color should not empty') true } return true } setShape(shape) { if (this.validName() && this.validColor()) { this.shape = shape; } } setSugar(sugar) { if (this.validName() && this.validColor()) { this.sugar = sugar; } } //...}
至此,我們?nèi)匀粵]有用到建造者模式,通過構造函數(shù)設置必填項,通過 set() 方法設置可選配置項,就能實現(xiàn)我們的設計需求。 但是我們再增加一下難度class Cake { constructor(name, color, shape, suger) { this.name = name; this.color = color; this.shape = shape; this.suger = suger; }}class CakeBuilder { valid() { //valid all params... } setName() { this.valid() //... return this; } setColor() { this.valid() //... return this; } setShage() { this.valid() //... return this; } setSuger() { this.valid() //... return this; } build() { const cake = new Cake() cake.shape = this.setShape() cake.suger = this.setSuger() cake.name = this.setName() cake.color = this.setColor() return cake }}const cake1 = new CakeBuilder() .setName('cake') .setColor('yellow') .setShape('heart') .setSugar('70%') .builder()//我們還可以把這長長的鏈式調(diào)用封裝起來,也就是指導者function diractor(builder) { return builder .setName('cake') .setColor('yellow') .setShape('heart') .setSugar('70%') .builder()}const cakeBuilder = new CakeBuilder()const cake2 = diractor(cakeBuilder)
Cake類中的成員變量,要在 Builder 類中重新再定義一遍,可以看出,建造者模式的使用有且只適合創(chuàng)建極為復雜的對象。在前端的實際業(yè)務中,在沒有這類極為復雜的對象的創(chuàng)建時,還是應該直接使用對象字面或工廠模式等方式創(chuàng)建對象。class Person { constructor(name) { this.name = name } getName() { return this.name }}class Student extends Person { constructor(name) { super(name) } sayHello() { console.log(`Hello, My name is ${this.name}`) }}let student = new Student("xiaoming")student.sayHello()
對于前端程序員來說,原型模式是一種比較常用的開發(fā)模式。這是因為,有別于 Java、C++ 等基于類的面向?qū)ο缶幊陶Z言,JavaScript 是一種基于原型的面向?qū)ο缶幊陶Z言。即便 JavaScript 現(xiàn)在也引入了類的概念,但它也只是基于原型的語法糖而已。class MyImg { static imgNode = document.createElement("img") constructor(selector) { selector.appendChild(this.imgNode); } setSrc(src) { this.imgNode = src }}const img = new MyImg(document.body)img.setSrc('xxx')
先看一下上面這段代碼,定義了一個MyImg類,接收一個選擇器,然后在這個選擇器下面創(chuàng)建一個img標簽并且暴露一個setSrc方法。class ProxyMyImg { static src = 'xxx本地預覽圖地址loading.gif' constructor(selector) { this.img = new Image this.myImg = new MyImg(selector) this.myImg.setSrc(this.src) } setSrc(src) { this.img.src = src this.img.onload = () => { this.myImg.setSrc(src) } }}const img = new ProxyMyImg(document.body)img.setSrc('xxx')
ProxyMyImg控制了客戶對MyImg的訪問,并且在此過程中加入一些額外的操作,比如在圖片加載好之前,先把img節(jié)點的src設置為一張本地的loading圖片。const mult = (...args) => { console.log('multing...') let res = 1 args.forEach(item => { res*=item }) return item}mult(2,3) //6mult(2,3,5)//30
加入緩存代理函數(shù)const mult = (...args) => { console.log('multing...') let res = 1 args.forEach(item => { res*=item }) return res }const proxyMult = (() => { const cache = {} return (...args) => { const key = [].join.call(args, ',') if (key in cache) { return cache[args] } return cache[key] = mult.apply(null, args) }})()proxyMult(1,2,3,4)// multing... 24proxyMult(1,2,3,4)//24
const p = new Proxy(target, handler)
target 要使用 Proxy 包裝的目標對象(可以是任何類型的對象,包括原生數(shù)組,函數(shù),甚至另一個代理)。
handler 一個通常以函數(shù)作為屬性的對象,各屬性中的函數(shù)分別定義了在執(zhí)行各種操作時代理 p 的行為。
const mult = (args) => { console.log('multing...') let res = 1 args.forEach(item => { res*=item }) return res}const handler = { cache: {}, apply: function(target, thisArg, args) { const key = [].join.call(args, ',') if(key in this.cache) { return this.cache[key] } return this.cache[key] = target(args) }}const proxyMult = new Proxy(mult, handler)proxyMult(1,2,3,4)//multing...//24proxyMult(1,2,3,4)//24
class Plan { fire() { console.log('發(fā)射子彈') }}class PlanDecorator { constructor(plan) { this.plan = plan } fire() { this.plan.fire() console.log('發(fā)射導彈') }}const plan = new Plan()const newPlan = new PlanDecorator(plan)newPlan.fire() //發(fā)射子彈 發(fā)射導彈
如果你熟悉TypeScript,那么他的代碼結構就是這樣的interface IA { init: () => void}class A implements IA { public init() { //... }}class ADecorator implements IA { constructor (a: IA) { this.a = a } init() { // 功能增強代碼 a.init() // 功能增強代碼 }}
著名的AOP就是通過裝飾者模式來實現(xiàn)的Function.prototype.before = function(beforeFn) { const _this = this //保存原函數(shù)的引用 return function() {// 返回包含了原函數(shù)和新函數(shù)的"代理函數(shù)" beforeFn.apply(this, arguments)// 執(zhí)行新函數(shù),修正this return _this.apply(this, arguments) // 執(zhí)行原函數(shù)并返回原函數(shù)的執(zhí)行結果,this不被劫持 }}
afterFunction.prototype.after = function(afterFn) { const _this = this return function() { const res = _this.apply(this, arguments) afterFn.apply(this, arguments) return res }}
around(環(huán)繞通知)Function.prototype.around = function(beforeFn, aroundFn) { const _this = this return function () { return _this.before(beforeFn).after(aroundFn).apply(this, arguments)// 利用之前寫的before 和after 來實現(xiàn)around }}
測試const log = (val) => { console.log(`日志輸出${val}`)}const beforFn = () => { console.log(`日志輸出之前先輸出${new Date().getTime()}`)}const afterFn = () => { console.log(`日志輸出之前再輸出${new Date().getTime()}`)}const preLog = log.before(beforFn)const lastLog = log.after(afterFn)const aroundLog = log.around(beforeFn, afterFn)preLog(11)lastLog(22)aroundLog(33)
class User { @checkLogin getUserInfo() { console.log('獲取用戶信息') }}// 檢查用戶是否登錄function checkLogin(target, name, descriptor) { let method = descriptor.value descriptor.value = function (...args) { // 校驗方法,假設這里可以獲取到用戶名/密碼 if (validate(args)) { method.apply(this, args) } else { console.log('沒有登錄,即將跳轉(zhuǎn)到登錄頁面...') } }}let user = new User()user.getUserInfo()
還有比較典型的在React中的高階組件function HOCDecorator(WrappedComponent){ return class HOC extends Component { render(){ const newProps = {param: 'HOC'}; return <div> <WrappedComponent {...this.props} {...newProps}/> </div> } }}@HOCDecoratorclass OriginComponent extends Component { render(){ return <div>{this.props.param}</div> }}
如果你熟悉mobx的話,你會發(fā)現(xiàn)里面的功能都支持裝飾器,當然我們也可以在Redux中使用裝飾器來實現(xiàn)connect函數(shù),這都是很方便的。 具體可以看阮一峰老師的講解 es6.ruanyifeng.com/#docs/decor…class GooleMap { show() { console.log('渲染地圖') }}class BaiduMap { display() { console.log('渲染地圖') }}class GaodeMap { show() { console.log('渲染地圖') }}// 上面三個類,如果我們用多態(tài)的思想去開發(fā)的話是很難受的,所以我們通過適配器來統(tǒng)一接口class BaiduAdaapterMap { show() { return new BaiduMap().display() }}
以適配器模式應用場景一般為class Car { constructor(brand) { this.brand = brand } speed() { //... }}class Transmission { constructor(trans) { this.trans = trans } action() { ... }}class AbstractCar { constructor(car, transmission) { this.car = car this.transmission } run () { this.car.speed() this.traansmission.action() //... }}
門面模式const myEvent = { // ... stop: e => { e.stopPropagation(); e.preventDefault(); }}
合模式class FileSystemNode { constructor(path) { this.path = path } countNumOfFiles() {} countSizeOfFiles() {} getPath() { return this.path }}class File extends FileSystemNode { constructor(path) { super(path) } countNumOfFiles () { return 1 } countSizeOfFiles() { //利用NodejsApi通過路徑獲取文件... }}class Directory extends FileSystemNode{ constructor(path) { super(path) this.fileList = [] } countNumOfFiles () { //... } countSizeOfFiles() { //... } addSubNode(fileOrDir) { this.fileList.push(fileOrDir) } removeSubNode(fileOrDir) { return this.fileList.filter(item => item !== fileOrDir) } }//如果我們要表示/** */ */leon */leon/aa.txt */leon/bb */leon/bb/cc.js const root = new Directory('/')const dir_leon = new Directory('/leon/')root.addSubNode(leon)const file_aa = new File('/leon/aa.txt')const dir_bb = new Directory('leon/bb')dir_leon.addSubNode(file_aa)dir_leon.addSubNode(dir_bb)const file_cc = new File('/leon/bb/cc.js')dir_bb.addSubNode(file_cc)
組合模式不好的一點是有可能一不小心就創(chuàng)建了大量的對象而降低性能或難以維護,所以我們接下來講的享元模式就是用來解決這個問題的。class CharacterStyle{ constructor(font, size, color) { this.font = font this.size = size this.color = color } equals(obj) { return this.font === obj.font && this.size === obj.size && this.color = obj.color }}class CharacterStyleFactory { static styleList = [] getStyle(font, size, color) { const newStyle = new CharacterStyle(font, size, color) for(let i = 0, style; style = this.styleList[i++];) { if (style.equals(newStyle)) { return style } } CharacterStyleFactory.styleList.push(newStyle) return newStyle }}class Character { constructor(c, style) { this.c = c this.style = style }}class Editor { static chars = [] appendCharacter(c, font, size, color) { const style = CharacterStyleFactory.getStyle(font, size, color) const character = new Character(c, style) Editor.chars.push(character) }}
如果我們不把樣式提取出來,我們每敲一個文字,都會調(diào)用 Editor 類中的 appendCharacter() 方法,創(chuàng)建一個新的 Character 對象,保存到 chars 數(shù)組中。如果一個文本文件中,有上萬、十幾萬、幾十萬的文字,那我們就要在內(nèi)存中存儲這么多 Character 對象。那有沒有辦法可以節(jié)省一點內(nèi)存呢?class Subject { registerObserver() { throw new Error('子類需重寫父類的方法') } removeObserver() { throw new Error('子類需重寫父類的方法') } notifyObservers() { throw new Error('子類需重寫父類的方法') }}class Observer{ update() { throw new Error('子類需重寫父類的方法') }}
然后根據(jù)模板來具體的實現(xiàn)class ConcreteSubject extends Subject { static observers = [] registerObserver(observer) { ConcreteSubject.observers.push(observer) } removeObserver(observer) { ConcreteSubject.observers = ConcreteSubject.observers.filter(item => item !== oberser) } notifyObservers(message) { for(let i = 0,observer; observer = ConcreteSubject.observers[i++];) { observer.update(message) } }}class ConcreteObserverOne extends Observer { update(message) { //TODO 獲得消息通知,執(zhí)行自己的邏輯 console.log(message) //... }}class ConcreteObserverTwo extends Observer { update(message) { //TODO 獲得消息通知,執(zhí)行自己的邏輯 console.log(message) //... }}class Demo { constructor() { const subject = new ConcreteSubject() subject.registerObserver(new ConcreteObserverOne()) subject.registerObserver(new ConcreteObserverTwo()) subject.notifyObservers('copy that') }}const demo = new Demo()
事實上,上面只是給出了一個大體的原理和思路,實際中的觀察者模式要復雜的多,比如你要考慮同步阻塞或異步非阻塞的問題,命名空間的問題,還有必須要先注冊再發(fā)布嗎? 而且項目中用了大量的觀察者模式的話會導致增加耦合性,降低內(nèi)聚性,使項目變得難以維護。class Tea { addCoffee() { console.log('加入咖啡') } addSuger() { throw new Error('子類需重寫父類的方法') } addMilk() { throw new Error('子類需重寫父類的方法') } addIce() { console.log('加入冰塊') } isIce() { return false // 默認不加冰 } addWater() { console.log('加水') } init() { this.addCoffee() this.addSuger() this.addMilk() if (this.isIce()) { this.addIce() } }}
模板寫好了,我們接下來做一杯拿鐵class Latte extends Tea { addSuger() { console.log('加糖') } addMilk() { console.log('加奶') } isIce() { return true }}const ice_latte = new Latte()ice_latte.init()
我們可以看到,我們不僅在父類中封裝了子類的算法框架,還將一些不會變化的方法在父類中實現(xiàn),這樣子類就直接繼承就可以了。其實,之前講組合模式的時候,也有用到了模板方法模式,你可以回頭看一眼。// 因為所有的策略類都實現(xiàn)相同的接口,所以我們可以通過模板來定義class Strategy { algorithmInterface() {}}class ConcreteStrategyA extends Strategy { algorithmInterface() { //具體的實現(xiàn) //... }}class ConcreteStrategyB extends Strategy { algorithmInterface() { //具體的實現(xiàn) //... }}//...
class StrategyFactory { strategies = new Map() constructor () { this.strategies.set("A", new ConcreteStrategyA()) this.strategies.set("B", new ConcreteStrategyB()) //... } getStrategy(type) { return type && this.strategies.get(type) } }
在實際的項目開發(fā)中,這個模式比較常用。最常見的應用場景是,利用它來避免冗長的 if-else 或 switch 分支判斷。不過,它的作用還不止如此。它也可以像模板模式那樣,提供框架的擴展點等等。class IHandler { handle() { throw new Error('子類需重寫這個方法') }}class HandlerA extends IHandler { handle() { let handled = false //... return handled }}class HandlerB extends IHandler { handle() { let handled = false //... return handled }}class HandlerChain { handles = [] addHandle(handle) { this.handles.push(handle) } handle() { this.handles.for(let i= 0, handler; handler = this.handles[i++];) { handled = handler.handle() if (handle) { break } } }}const chain = new HandlerChain()chain.addHandler(new HandlerA())chain.addHandler(new HandlerB())chain.handle()
第二種方法用到了鏈表的實現(xiàn)方式class Handler { successor = null setSuccessor(successor) { this.successor = successor } handle() { const isHandle = this.doHandle() if (!isHandle && !!this.successor) { this.successor.handle } } doHandle() { throw new Error('子類需重寫這個方法') }}class HandlerA extends Handler { doHandle() { let handle = false //... return handle }}class HandlerB extends Handler { doHandle() { let handle = false //... return handle }}class HandlerChain { head = null tail = null addHandler(handler) { handler.setSuccessor(null) if (head === null) { head = handler tail = handler return } tail.setSuccessor(handler) tail = handler } handle() { if (!!head) { head.handle() } }}const chain = new HandlerChain()chain.addHandler(new HandlerA())chain.addHandler(new HandlerB())chain.handle()
還記得我們之前說裝飾者模式說到的AOP嗎,我們可以把它稍微改造一下,也可以變成職責鏈的方式。Function.prototype.after = function(afterFn) { let self = this return function() { let res = self.apply(this, arguments) if (res === false) { afterFn.apply(this, arguments) } return ret }}const res = fn.after(fn1).after(fn2).after(fn3)
可以看出我們傳進去的afterFn函數(shù)如果返回false的話,會連著這條鏈一直傳下去,直到最后一個,一旦返回true,就不會將請求往后傳遞了。class ArrayIterator { constructor( arrayList) { this.cursor = 0 this.arrayList = arrayList } hasNext() { return this.cursor !== this.arrayList.length } next() { this.cursor++ } currentItem() { if(this.cursor > this.arrayList.length) { throw new Error('no such ele') } return this.arrayList[this.cursor] } }
在上面的代碼實現(xiàn)中,我們需要將待遍歷的容器對象,通過構造函數(shù)傳遞給迭代器類。實際上,為了封裝迭代器的創(chuàng)建細節(jié),我們可以在容器中定義一個 iterator() 方法,來創(chuàng)建對應的迭代器。為了能實現(xiàn)基于接口而非實現(xiàn)編程,我們還需要將這個方法定義在 ArrayList 接口中。具體的代碼實現(xiàn)和使用示例如下所示:class ArrayList { constructor(arrayList) { this.arrayList = arrayList } iterator() { return new ArrayIterator(this.arrayList) }}const names = ['lee', 'leon','qing','quene']const arr = new ArrayList(names)const iterator = arr.iterator()iterator.hasNext()iterator.currentItem()iterator.next()iterator.currentItem()
上面我們只實現(xiàn)了數(shù)組的迭代器,關于對象的迭代器,他們的原理差不多,你可以自己去實現(xiàn)以下。class MarioStateMachine { constructor() { this.score = 0 this.currentState = new SmallMario(this) } obtainMushRoom() { this.currentState.obtainMushRoom() } obtainCape() { this.currentState.obtainCape() } obtainFireFlower() { this.currentState.obtainFireFlower() } meetMonster() { this.currentState.meetMonster() } getScore() { return this.score } getCurrentState() { return this.currentState } setScore(score) { this.score = score } setCurrentState(currentState) { this.currentState = currentState }}class Mario { getName() {} obtainMushRoom() {} obtainCape(){} obtainFireFlower(){} meetMonster(){}}class SmallMario extends Mario { constructor(stateMachine) { super() this.stateMachine = stateMachine } obtainMushRoom() { this.stateMachine.setCurrentState(new SuperMario(this.stateMachine)) this.stateMachine.setScore(this.stateMachine.getScore() + 100) } obtainCape() { this.stateMachine.setCurrentState(new CapeMario(this.stateMachine)) this.stateMachine.setScore(this.stateMachine.getScore() + 200) } obtainFireFlower() { this.stateMachine.setCurrentState(new FireMario(this.stateMachine)) this.stateMachine.setScore(this.stateMachine.getScore() + 300) } meetMonster() { // do something }}class SuperMario extends Mario { constructor(stateMachine) { super() this.stateMachine = stateMachine } obtainMushRoom() { // do nothing... } obtainCape() { this.stateMachine.setCurrentState(new CapeMario(this.stateMachine)) this.stateMachine.setScore(this.stateMachine.getScore() + 200) } obtainFireFlower() { this.stateMachine.setCurrentState(new FireMario(this.stateMachine)) this.stateMachine.setScore(this.stateMachine.getScore() + 300) } meetMonster() { this.stateMachine.setCurrentState(new SmallMario(this.stateMachine)) this.stateMachine.setScore(this.stateMachine.getScore() - 100) }}//CapeMario FireMario格式相同//使用const mario = new MarioStateMachine()mario.obtainMushRoom()mario.getScore()
MarioStateMachine 和各個狀態(tài)類之間是雙向依賴關系。MarioStateMachine 依賴各個狀態(tài)類是理所當然的,但是,反過來,各個狀態(tài)類為什么要依賴 MarioStateMachine 呢?這是因為,各個狀態(tài)類需要更新 MarioStateMachine 中的兩個變量,score 和 currentState。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() } }}class Mediator { constructor(a, b) { this.a = a this.b = b } setA() { let number = this.b.number this.a.setNumber(number * 10) } setB() { let number = this.a.number this.b.setNumber(number / 10) }}let a = new A()let b = new B()let m = new Mediator(a, b)a.setNumber(10, m)console.log(a.number, b.number)b.setNumber(10, m)console.log(a.number, b.number)
public interface Visitor { void visit(Engine engine); void visit(Body body); void visit(Car car);}public class PrintCar implements Visitor { public void visit(Engine engine) { System.out.println("Visiting engine"); } public void visit(Body body) { System.out.println("Visiting body"); } public void visit(Car car) { System.out.println("Visiting car"); }}public class CheckCar implements Visitor { public void visit(Engine engine) { System.out.println("Check engine"); } public void visit(Body body) { System.out.println("Check body"); } public void visit(Car car) { System.out.println("Check car"); }}public interface Visitable { void accept(Visitor visitor);}public class Body implements Visitable { @Override public void accept(Visitor visitor) { visitor.visit(this); }}public class Engine implements Visitable { @Override public void accept(Visitor visitor) { visitor.visit(this); }}public class Car { private List<Visitable> visit = new ArrayList<>(); public void addVisit(Visitable visitable) { visit.add(visitable); } public void show(Visitor visitor) { for (Visitable visitable: visit) { visitable.accept(visitor); } }}public class Client { static public void main(String[] args) { Car car = new Car(); car.addVisit(new Body()); car.addVisit(new Engine()); Visitor print = new PrintCar(); car.show(print); }}
一般來說,訪問者模式針對的是一組類型不同的對象。不過,盡管這組對象的類型是不同的,但是,它們繼承相同的父類或者實現(xiàn)相同的接口。//備忘類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()) //444editor.getContentFromMemento(careTaker.get(1))console.log(editor.getContent()) //333editor.getContentFromMemento(careTaker.get(0))console.log(editor.getContent()) //222
備忘錄模式也叫快照模式,具體來說,就是在不違背封裝原則的前提下,捕獲一個對象的內(nèi)部狀態(tài),并在該對象之外保存這個狀態(tài),以便之后恢復對象為先前的狀態(tài)。這個模式的定義表達了兩部分內(nèi)容:一部分是,存儲副本以便后期恢復;另一部分是,要在不違背封裝原則的前提下,進行對象的備份和恢復。// 接收者類class Receiver { execute() { console.log('接收者執(zhí)行請求') } } // 命令者class Command { constructor(receiver) { this.receiver = receiver } execute () { console.log('命令'); this.receiver.execute() }}// 觸發(fā)者class Invoker { constructor(command) { this.command = command } invoke() { console.log('開始') this.command.execute() }} // 開發(fā)商const developer = new Receiver(); // 售樓處 const order = new Command(developer); // 買房const client = new Invoker(order); client.invoke()
在一些面向?qū)ο蟮恼Z言中,函數(shù)不能當做參數(shù)被傳遞給其他對象,也沒法賦值給變量,借助命令模式,我們將函數(shù)封裝成對象,這樣就可以實現(xiàn)把函數(shù)像對象一樣使用。但是在js中函數(shù)當成參數(shù)被傳遞是再簡單不過的事情了,所以上面的代碼我們也可以直接用函數(shù)來實現(xiàn)。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; } } class MultiplicationExpression { interpret(context) { if (!(context instanceof Context)) { throw new Error("TypeError") } context.sum *= context.sum } } class DivisionExpression { interpret(context) { if (!(context instanceof Context)) { throw new Error("TypeError") } context.sum /= context.sum } } // MultiplicationExpression和DivisionExpression省略 /** 以下是測試代碼 **/ const context = new Context(); // 依次添加: 加法 | 加法 | 減法 表達式 context.add(new PlusExpression()); context.add(new PlusExpression()); context.add(new MinusExpression()); context.add(new MultiplicationExpression()); context.add(new MultiplicationExpression()); context.add(new DivisionExpression()); // 依次執(zhí)行: 加法 | 加法 | 減法 表達式 context.list.forEach(expression => expression.interpret(context)); console.log(context.sum);
解釋器模式的代碼實現(xiàn)比較靈活,沒有固定的模板。我們前面說過,應用設計模式主要是應對代碼的復雜性,解釋器模式也不例外。它的代碼實現(xiàn)的核心思想,就是將語法解析的工作拆分到各個小類中,以此來避免大而全的解析類。一般的做法是,將語法規(guī)則拆分一些小的獨立的單元,然后對每個單元進行解析,最終合并為對整個語法規(guī)則的解析。關鍵詞:設計,模式,整理
微信公眾號
版權所有? 億企邦 1997-2025 保留一切法律許可權利。