内容简介:Composite Pattern 是 OOP 中著名的 Design Pattern,無論是macOS High Sierra 10.13.3.NET Core SDK 2.1.101
Composite Pattern 是 OOP 中著名的 Design Pattern,無論是 物件
或 容器
,都能使用相同 interface 一視同仁的操作,F# 既然是 Function First Language,就讓我們以 function 的角度重新實現 Composite Pattern。
Version
macOS High Sierra 10.13.3
.NET Core SDK 2.1.101
JetBrains Rider 2018.1
F# 4.1
User Story
由購物車計算商品價錢,但在活動期間,Apple 產品組合有特惠 :
- MacBook Pro 15” :6 萬
- iPad Air :1 萬
- Apple Watch :1 萬
- Apple 套餐組合 : (Macbook Pro + iPad Air + Apple Watch) 總價打九折:
(6 + 1 + 1) * 0.9
= 7.2 萬
若同時買 MacBook Pro + iPad Air + Apple Watch + Apple 套餐為 15.2 萬。
Task
直接使用 FP 的思維完成需求。
Definition
Composite Pattern
當 單一資料
與 組合資料
同時存在時,讓 client 有一致性的操作方式
首先思考 Composite Pattern 的本質:
- Leaf 與 Composite 之間的 interface 必須相同
- Client 操作 Leaf 與 Composite 都一視同仁
只要能達到這兩個目標,就算完成了 Composite Pattern。
OOP 思考方式
- 為了讓 Leaf 與 Composite 的 interface 要相同,所以必須訂出共同的 interface
- Composite 必須以 state 記住 Leaf,並根據 state 加以操作
- Client 以相同方式操作 Composite 與 Leaf
FP 思考方式
- Data 與 function 分開
- 將 Leaf 與 Composite 都設定成 type
- Composite 無需以 state 記住 Leaf,在建立 data 時就建立 Leaf
- 只要建立單一 function 操作所有 data 即可,如此 Composite 與 Leaf 很自然地 interface 都相同
- 由於 Composite 與 Leaf 的 interface 都相同,所以 Composite 會以 recursive 方式呼叫 Leaf 的 function
Implementation
Product.fs
namespace ClassLibrary type Apple = | MacBookPro of double | IPadAir of double | AppleWatch of double | AppleCombo of Apple List module Product = let macbookPro = MacBookPro 60000.0 let ipadAir = IPadAir 10000.0 let appleWatch = AppleWatch 10000.0 let appleCombo = (AppleCombo) [ macbookPro; ipadAir; appleWatch ] let rec getPrice product = match product with | MacBookPro(price) | IPadAir(price) | AppleWatch(price) -> price | AppleCombo(products) -> products |> List.sumBy (fun elm -> getPrice elm) |> (fun elm -> elm * 0.9)
第 3 行
type Apple = | MacBookPro of double | IPadAir of double | AppleWatch of double | AppleCombo of Apple List
將各產品都設定成 type,其中 AppleCombo of Apple List
表示 AppleCombo
為各種 Apple 產品的組合。
10 行
let macbookPro = MacBookPro 60000.0 let ipadAir = IPadAir 10000.0 let appleWatch = AppleWatch 10000.0
設定各產品的定價。
13 行
let appleCombo = (AppleCombo) [ macbookPro; ipadAir; appleWatch ]
設定 Apple 套餐該包含哪些產品, (AppleCombo)
轉型是必要的,若沒有轉型,Type Inference 會推斷成 Apple list
,導致編譯錯誤,我們實際需要的型別為 Apple
。
再次證明 F# 寫程式不需要型別,但型別的檢查依然非常嚴格,這就是 Type Inference 優越的地方。
16 行
let rec getPrice product = match product with | MacBookPro(price) | IPadAir(price) | AppleWatch(price) -> price | AppleCombo(products) -> products |> List.sumBy (fun elm -> getPrice elm) |> (fun elm -> elm * 0.9)
getPrice()
計算單一商品與 Apple 套餐。
match product with | MacBookPro(price) | IPadAir(price) | AppleWatch(price) -> price
Pattern Matching 會使用型別判斷,且將該型別的值 price 直接 extract 出來。
| AppleCombo(products) -> products |> List.sumBy (fun elm -> getPrice elm) |> (fun elm -> elm * 0.9)
由 AppleCombo
extract 出 products
,其型別為 List
,可由 List.sumBy()
去計算總和,值得注意的是再度使用 getPrice()
計算每個產品的價錢,因此 getPrice()
必須宣告成 rec
。
最後再將 List.sumBy()
的結果打九折。
我們可以發現在處理 多型
的議題上,OOP 與 FP 採用不同的方式,由於 OOP 是 data 與 function 合一,因此 getPrice()
會寫在各 data class 上,然後用 interface 強迫各 class 都要實作 getPrice()
;但 FP 是 data 與 function 分開,因此只要一個 getPrice()
,然後使用 Pattern Matching 根據不同型別有相對應的處理。
但無論 OOP 或 FP,儘管處理方式不同,但對於 client 來說,只要是 Apple
型別,都使用相同的 getPrice()
,符合 開放封閉原則
的要求。
ShoppingCart.fs
namespace ClassLibrary module ShoppingCart = let calculatePrice products = products |> List.sumBy (fun elm -> Product.getPrice elm)
products
的型別為 Apple list
,因此每個 element 都是 Apple
型別,都可放心使用 getPrice()
,這就是 Composite Pattern 的精華。
Program.fs
// Learn more about F# at http://fsharp.org open System open ClassLibrary [<EntryPoint>] let main argv = [ Product.macbookPro Product.ipadAir Product.appleWatch Product.appleCombo ] |> ShoppingCart.calculatePrice |> printf "%.0f" 0
Client 呼叫 ShoppingCart.calculatePrice()
計算購物車內所有商品。
Summary
回想 Composite Pattern 的本質:
- Leaf 與 Composite 之間的 interface 必須相同
- Client 操作 Leaf 與 Composite 都一視同仁
FP 雖然沒有特別定義 interface,但所有 Apple
type 的 data 統一都適用於 getPrice()
function,由於在 getPrice()
內使用了 Pattern Matching,只要其中有一個 type 回傳型別不同,compiler 編譯就會報錯,與原本 Composite Pattern 定義 interface 本質相同。
對於 Apple
type,統一使用 getPrice()
function 處理,與原本 Composite Pattern 在 Component
interface 下的 Leaf
與 Composite
都一視同仁的本質相同。
Conclusion
- Composite Pattern 本質就是
多型
,OOP 使用 interface 讓物件
與容器
實踐相同 interface,讓兩者能使用相同的方式操作;FP 使用單一 function,再配合 Pattern Matching 對相同 type 的各種物件有不同的實作。雖然方式不同,但讓 client 以相同方式操作的本質都相同,也符合開放封閉原則
的要求
Sample Code
完整的範例可以在我的 GitHub 上找到
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- RecyclerView使用指南(一)—— 基本使用
- 如何使用Meteorjs使用URL参数
- 使用 defer 还是不使用 defer?
- 使用 Typescript 加强 Vuex 使用体验
- [译] 何时使用 Rust?何时使用 Go?
- UDP协议的正确使用场合(谨慎使用)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。