裝飾器模式的定義
裝飾器模式(decorator pattern):允許向一個先有的對象增添新的功能,同時又不改變其解構。
裝飾器模式的優點
-
裝飾器模式與繼承關係的目的都是要擴展對象的功能,但是裝飾器模式可以提供比繼承更多的靈活性。裝飾器模式允許系統動態決定貼上一個需要的裝飾,或者除掉一個不需要的裝飾。繼承關係是不同,繼承關係是靜態的,它在系統運行前就決定了
-
通過使用不同的具體裝飾器以及這些裝飾類的排列組合,設計師可以創造出很多不同的行爲組合
裝飾器模式的缺點
- 由於使用裝飾器模式,可以比使用繼承關係需要較少數目的類。使用較少的類,當然使設計比較易於進行。但是另一方面,由於使用裝飾器模式會產生比使用繼承關係更多的對象,更多的對象會使得查錯變得困難,特別是這些對象看上去都很像。
裝飾器模式和適配器模式的區別
-
適配器模式的意義是要將一個接口轉變成另外一個接口,它的目的是通過改變接口來達到重複使用的目的
-
裝飾器模式不要改變被裝飾對象的接口,而是恰恰要保持原有的接口哦,但是增強原有接口的功能,或者改變原有對象的處理方法而提升性能
示例代碼
恩,來一個場景:假設我們要去報名上學,通常學校都是漢語(普通話), 但是我們爲了更好的教育,可能會報雙語學校,那麼我麼就可以學會英語和漢語了
實現思路:
- 創建學校實例,
- 創建適配器,即雙語學校
- 創建學校實例,調用learn方法
// 裝飾器模式
// 裝飾只是讓原來的功能更強大,並不改變原來的功能
// 1. 學校實例
class School {
constructor (name) {
this.name = name
}
learn (language) {
console.log(`learn ${language}`)
}
}
// 2. 創建適配器(雙語學校類)
class DoubleLanguageSchool {
constructor (name) {
this.school = new Durk(name)
}
learn (language) {
this.school.learn(language)
console.log(`learn English`)
}
}
// 3. 創建實例,調用learn方法
let s = new DoubleLanguageSchool('Leo')
s.learn('chinese')
常見開發場景
- 開發產品,複用類,任意組合類, 以咖啡店爲例,我們在點咖啡的時候,除了最簡單的咖啡,可能需要加奶,加糖,加泡沫…相對的價格什麼的都會變化,我們這樣子就需要使用適配模式了
// 某些情況下裝飾模式回優於原型繼承模式
// 1. 咖啡類
class Coffee {
make (water) {
return `${water}+咖啡`
}
cost () {
return 10
}
}
// 2. 牛奶咖啡
class MilkCoffee {
constructor (parent) {
this.parent = parent
}
make (water) {
return `${this.parent.make(water)}+牛奶`
}
cost () {
return this.parent.cost() + 5
}
}
// 加糖咖啡
class SugarCoffee {
constructor(parent) {
this.parent = parent
}
make(water) {
return `${this.parent.make(water)}+糖`
}
cost() {
return this.parent.cost() + 3
}
}
// 我們通過一下類, 就可以自由組合
let coffee = new Coffee()
let milkCoffee = new MilkCoffee(coffee)
let sugerCoffee = new SugarCoffee(milkCoffee)
let ret = milkCoffee.make('水')
let cost = milkCoffee.cost()
console.log(ret, cost)
let ret2 = sugerCoffee.make('水')
let cost2 = sugerCoffee.cost()
console.log(ret2, cost2)
-
處理事件之前和之後處理額外的邏輯
// AOP 就是在覈心函數執行之前或者之後執行額外邏輯,俗稱切面編程 /** * @desc 定義before方法 * @param {function} beforeFn */ Function.prototype.before = function (beforeFn) { let self = this; return function () { beforeFn.apply(this, arguments) self.apply(this, arguments) } } /** * @desc 定義after方法 * @param {function} afterFn */ Function.prototype.after = function (afterFn) { let self = this; return function () { self.apply(this, arguments) afterFn.apply(this, arguments) } } /** * @desc 購物 * @param {number} money * @param {string} product */ function buy(money, product) { console.log(`花${money}買${product}`) } buy = buy.before(() => {console.log('銀行取錢')}) buy = buy.after(()=>{console.log('將剩餘的錢存銀行')}) // 調用 buy(1888, '遊戲機')
-
使用場景之埋點
-
服務端腳本
let Koa = require('koa') let app = new Koa() const _ = require('koa-route'); let json = {} const res = { report: (ctx) => { console.log(ctx.query) let name = ctx.query.name if (json[name]) { json[name]++ } else { json[name] = 1 } ctx.body = 'hello' }, json: (ctx) => { ctx.body = json } }; app.use(_.get('/report', res.report)); app.use(_.get('/', res.json)); app.listen(3000)
-
客戶管代碼
<!-- 使用場景之埋點 --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <button data-name="watermelon" id="watermelon">西瓜</button> <button data-name="apple" id="apple">蘋果</button> <script> let watermelon = document.querySelector('#watermelon') let apple = document.querySelector('#apple') /** * @desc 定義after方法 * @param {function} afterFn */ Function.prototype.after = function (afterFn) { let self = this; return function () { self.apply(this, arguments) afterFn.apply(this, arguments) } } function click() { console.log('你點擊了'+ this.dataset.name) } click = click.after(function () { let img = new Image() img.src = `http://localhost:3000/report?name=${this.dataset.name}` }) Array.from(document.querySelectorAll('button')).forEach(btn => { btn.addEventListener('click', click) }) </script> </body> </html>
-
-
使用場景之表單驗證
<!-- 使用場景之埋點 --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>表單效驗</title> </head> <body> 用戶名 <input type="text" id="username"> 密碼 <input type="password" id="password"> <button id="submit">提交</button> <script> /** * @desc 定義before方法 * @param {function} beforeFn */ Function.prototype.before = function (beforeFn) { let self = this; return function () { // 這裏要拿到上一次效驗完畢的驗證結果 if (beforeFn.apply(this, arguments)) { self.apply(this, arguments) } } } function submit(params) { console.log('提交表單') } submit = submit.before(function () { let username = document.getElementById('username').value if (!username) { alert('用戶名不能爲空') return false } return true }) submit = submit.before(function () { let username = document.getElementById('username').value if (username.length < 4) { alert('用戶名長度最少爲4') return false } return true }) document.getElementById('submit').addEventListener('click', submit) </script> </body> </html>
-
其他使用場景
- axios的請求發起之前的函數
- vue的路由調整鉤子函數
- 很多場景,就不一一贅述了.
總結
多學習,有助於睡眠