JavaScript Decorator Pattern

在原有的事物上,一點一滴地加上一些裝飾,這樣的概念套用在程式設計上,其實就是裝飾模式。

以計算金額來看,最終的計算結果是一個價格。但是這個價格也許還要再經過一些加工,例如markup、稅額等等,最終呈現的金額可能會因為各種條件而有所不同,這就可以利用裝飾模式來處理

一樣是從書中範例開始模仿學習,書中的範例提供了兩種方式,都能夠達到目的,這邊練習選擇比較簡單易懂的方式。

範例取自JavaScript設計模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// /src/Sale.js
var decorators = require('./Decorators')

class Sale {
constructor(price) {
this.price = price > 0 ? price : 100
this.decorators_list = []
this.decorators = decorators
}

decorate(decorator) {
this.decorators_list.push(decorator)
}

GetPrice() {
let price = this.price
let max = this.decorators_list.length
let name

for (let i = 0; i < max; i++) {
name = this.decorators_list[i]
price = this.decorators[name].GetPrice(price)
}
return price
}

}

module.exports = Sale

這邊的重點在於定義流程,介面。並不實作細節。也就是說,Sale這個類別(姑且讓我這樣稱呼吧),他本身提供了兩個方法,一個方法是用來記錄有多少個decorator要使用。將他放在陣列裡面等候GetPrice()呼叫使用;而GetPrice()就更簡單了,將每一個decorator抓出來,然後把一開始的price經過剛才所設定的每一個decorator處理,最終再return這個結果。再更簡單的說,GetPrice()她只是將我們剛才設定要用的decorator,拿出來用而已。(實際的decorator部分,則是透過ctor的時候,從另外一隻檔案把資料抓過來,然後等著GetPrice()用this.decorators[name]這個方式去呼叫。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// /src/Decorators.js
var decorators = {}
decorators.fedtax = {
GetPrice: function (price) {
return price + price * 5 / 100
}
}

decorators.quebec = {
GetPrice: function (price) {
return price + price * 7.5 / 100
}
}
decorators.money = {
GetPrice: function (price) {
return price.toFixed(2)
}
}
module.exports = decorators

裝飾模式的各種實作細節,都被放在這個物件裡面,如果是較為複雜的實作細節,那就可以再考慮針對這個部分來進行重構設計,不過目前為止,維持這樣就可以了。

這裡的重點在於將各種裝飾方法的名稱與實作方法這對key-value,放在這個decorators的物件內,方便我們定義的Sale物件來呼叫。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// /test/Sale.test.js
var mocha = require('mocha')
var chai = require('chai')
chai.should()

var SaleObj = require('../src/Sale')
describe('Sale', () => {
it('#GetPrice()', () => {
let expected = '112.88'
let sale = new SaleObj(100)
sale.decorate('fedtax')
sale.decorate('quebec')
sale.decorate('money')
let actual = sale.GetPrice()
actual.should.be.equal(expected)
});
});

最終的程式碼與書上的並沒有太多改變,從測試程式中可以看到,模擬Client端呼叫使用的時候是透過一行一行的sale.decorate()呼叫,選擇不一樣的裝飾方法。這樣的方式在未來調整程式碼時,抽換演算法或是讓其他人瀏覽這份程式碼,都可以盡快的理解程式碼的意圖。

Code在文章都貼出來了….不過還是一樣照慣例附上程式碼….