商品信息改變帶來的煩惱
Talk is cheap. Show me the code. (譯: 屁話少說, 放碼過來)
以下所有代碼參見Design pattern transformation.
// 商品的信息: 價格 & 折扣
const data = {
price: 100,
discount: 0.8
}
// 顧客信息: 是否威會員 & 購買數量 & 總消費 & 購買時間戳
const customer = {
"VIP": true,
"quantity": 10,
"total": 0,
}
// 總消費計算方式
total = (info) => {
if(!info.VIP) {
info.total = data.price * info.quantity;
} else {
info.total = data.price * data.discount * info.quantity;
}
}
total(customer);
console.log('customer', customer);
// customer { VIP: true, quantity: 10, total: 800 }
從代碼中很容易看得出來, 我們就是想實現一個簡單的計費功能. 可現實中, 商品的價格可能並不是一成不變的.
data.price = 200
價格變動後, 我們需要及時地獲取總消費, 那麼就必須重新調用下 total計費.
total(customer);
console.log('customer', customer);
// customer { VIP: true, quantity: 10, total: 1600 }
這是一個大數據時代, 任何數據都有價值. 現在, 我們還想要每次購買時的時間點.
const customer = {
"VIP": true,
"quantity": 10,
"total": 0,
+ "timeStamp": 0
}
// 獲取購買時間
purchaseTime = (info) => {
info.timeStamp = Date.now();
}
於是, 我們需要執行的函數就多了一個.
total(customer)
purchaseTime(customer)
console.log('customer', customer)
// { VIP: true, quantity: 10, total: 1600, timeStamp: 1542293676297 }
如果我們的需求還有很多, 而且不知一個customer呢. 那麼, 每次價格變化我們需要執行很多步驟, 每次啊, 麻煩得很.
+ const customer1 = {
+ "VIP": false,
+ "quantity": 8,
+ "total": 0,
+ "timeStamp": 0
+ }
total(customer)
purchaseTime(customer)
func(customer)
...
funcN(customer1)
total(customer1)
purchaseTime(customer1)
func(customer1)
...
funcN(customer)
...
funcN(customerN)
現在我們就對上面的代碼進行觀察者模式改造.
用觀察者模式改造
從上面的例子中🌰🀄️不難看出, 每次價格變化時, 我們都需要重複調用滿足需求的方法. 不妨想想, 如果我們把這些方法存儲起來, 等到價格變化時再去統一調用, 豈不是很方便. 那麼問題來了, 這和之前所說的觀察者模式(從觀察者模式說起)有什麼區別呢? 在此, 我們試着用觀察者模式改造下.
首先觀察者模式都是一個套路. 先一個類維護一個列表, 對列表有增刪和通知更新功能. 另一個類則是提供了更新接口.
// 觀察目標類
class Subject {
constructor() {
this.observerList = []
}
addObserver(observer) {
this.observerList.push(observer)
}
notify(params) {
this.observerList.forEach(observer => {
observer.update(params)
})
}
}
// 觀察者類
class Observer {
constructor(fn) {
this.update = fn
}
}
接着, 把我們想要調用的方法包裝一下, 存儲起來.
// 將要重複使用的包裝一下
observer1 = new Observer(total)
observer2 = new Observer(purchaseTime)
// 存起來
let subject = new Subject()
subject.addObserver(observer1)
subject.addObserver(observer2)
每次價格改變時, 只需要通知一下即可.
// 調整商品價格
data.price = 100
subject.notify(customer)
subject.notify(customer1)
改造結束. 初看起來, 可能變得繁瑣了. 但是, 遇到複雜的情況, 這不失是一個好辦法. 接下來, 我們看看結合Objec.defineProperty會有什麼驚喜.
與Objec.defineProperty結合
支付寶的花唄都可以自己還錢了🤣, 我們爲什麼還要別人管着😏. 大家都知道經過Objec.defineProperty處理的對象, 在設置和獲取對象屬性的時候, 會自動觸發響應set和get方法. 利用這一點, 我們就可以做到生活自理了. 熟悉的配方, 熟悉的味道. 熟悉的套路我們不妨再走一遍.
// 觀察目標類
class Dependency {
constructor() {
this.watcherList = []
}
addObserver(observer) {
this.watcherList.push(observer)
}
notify(params) {
this.watcherList.forEach(watcher => {
watcher.update(params)
})
}
}
// 觀察類
class Watcher {
constructor(fn) {
this.update = fn
}
}
我們此行的目的, 是要在data.price 或data.discount改變時, 程序能夠自動觸發, 得到我們想要的結果. 換句話說, 通知更新的時機是在設置data.price或data.discount的時候.
Object.keys(data).forEach(key => {
let value = data[key]
const dep = new Dependency()
Object.defineProperty(data, key, {
set(newVal) {
value = newVal
dep.notify()
},
get() {
return value
}
})
})
對象的每個屬性都給了一個依賴實例, 管理自己的依賴. 考慮到customer有很多個, 需要通知到位. 另外, 添加依賴和管理依賴, 前者是因, 後者是果. 在管理之前我們需要想好怎麼添加依賴. 回頭看一看.
// 總消費計算方式
total = (info) => {
if(!info.VIP) {
info.total = data.price * info.quantity;
} else {
info.total = data.price * data.discount * info.quantity;
}
}
// 獲取購買時間
purchaseTime = (info) => {
info.timeStamp = Date.now();
}
我們發現, total函數依賴於data.price或data.discount的. 如果我們在獲取屬性時去添加依賴倒是一個好時機.
class Dependency {
// 省略
}
+ Dependency.targey = null;
class Watcher {
constructor(fn, key) {
this.update = fn
+ this.key = key
+ this.value = this.getter()
}
+ getter() {
+ Dependency.targey = this;
+ // 出發下面的get()
+ this.value = data[this.key];
+ Dependency.targey = null;
+ }
}
Object.keys(data).forEach(key => {
let value = data[key]
const dep = new Dependency()
Object.defineProperty(data, key, {
set(newVal) {
value = newVal
dep.notify()
},
get() {
+ if (Dependency.targey) {
+ dep.addObserver(Dependency.targey)
+ }
return value
}
})
})
然而purchaseTime方法裏並沒有data.price或data.discount可以設置. 所以這個方法行不通. 那麼, 乾脆緊接着依賴實例去添加依賴吧. 同時考慮到多個customer, 我們封裝下.
// 與defineProperty結合
function defineReactive(data, watcherList, funcList) {
Object.keys(data).forEach(key => {
let value = data[key]
const dep = new Dependency()
funcList.forEach(func => {
dep.addObserver(new Watcher(func))
})
Object.defineProperty(data, key, {
set(newVal) {
value = newVal
watcherList.forEach(watcher => {
dep.notify(watcher)
})
},
get() {
return value
}
})
})
}
defineReactive(data, [customer, customer1], [total, purchaseTime])
大功告成, 價格變動時, 我們就會自動獲取到想要的結果了. 我都能自理了, 你花唄爲嘛還不能自己還錢呢😒