内容简介: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协议的正确使用场合(谨慎使用)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Masterminds of Programming
Federico Biancuzzi、Chromatic / O'Reilly Media / 2009-03-27 / USD 39.99
Description Masterminds of Programming features exclusive interviews with the creators of several historic and highly influential programming languages. Think along with Adin D. Falkoff (APL), Jame......一起来看看 《Masterminds of Programming》 这本书的介绍吧!