如何使用 F# 實現 Composite Pattern?

栏目: ASP.NET · 发布时间: 6年前

内容简介: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 萬

如何使用 F# 實現 Composite Pattern?

若同時買 MacBook Pro + iPad Air + Apple Watch + Apple 套餐為 15.2 萬。

Task

直接使用 FP 的思維完成需求。

Definition

Composite Pattern

單一資料組合資料 同時存在時,讓 client 有一致性的操作方式

如何使用 F# 實現 Composite Pattern?

首先思考 Composite Pattern 的本質:

  1. Leaf 與 Composite 之間的 interface 必須相同
  2. 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# 實現 Composite Pattern?

再次證明 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 的本質:

  1. Leaf 與 Composite 之間的 interface 必須相同
  2. 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 下的 LeafComposite 都一視同仁的本質相同。

Conclusion

  • Composite Pattern 本質就是 多型 ,OOP 使用 interface 讓 物件容器 實踐相同 interface,讓兩者能使用相同的方式操作;FP 使用單一 function,再配合 Pattern Matching 對相同 type 的各種物件有不同的實作。雖然方式不同,但讓 client 以相同方式操作的本質都相同,也符合 開放封閉原則 的要求

Sample Code

完整的範例可以在我的 GitHub 上找到


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

高效程序的奥秘

高效程序的奥秘

沃瑞恩 / 冯速 / 机械工业出版社 / 2004-5 / 28.00元

本书适合程序库、编译器开发者及追求优美程序设计的人员阅读,适合用作计算机专业高年级学生及研究生的参考用书。  本书直观明了地讲述了计算机算术的更深层次的、更隐秘的技术,汇集了各种编辑的小技巧,包括常购的任务的小算法,2的幂边界和边界检测、位和字节的重排列、整数除法和常量除法、针对整数的基涵义,空间填充曲线、素数公式等。一起来看看 《高效程序的奥秘》 这本书的介绍吧!

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具