如何使用 F# 實現 Composite Pattern?

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

内容简介: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 上找到


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

查看所有标签

猜你喜欢:

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

另一个地球

另一个地球

[美]马克·格雷厄姆、威廉·H·达顿 / 胡泳、徐嫩羽 / 电子工业出版社 / 2015-10-1 / 78

互联网在日常工作和生活中扮演日益重要的角色,互联网将如何重塑社会?本书通过汇集有关互联网文化、经济、政治角色等问题的研究成果,提供了特定社会制度背景下解决这一问题的根本办法。 关于互联网的研究是蓬勃发展的崭新领域,牛津大学互联网研究院(OII)作为创新型的跨学科学院,自成立起就专注于互联网研究。牛津大学互联网研究院关于互联网+社会的系列讲座在一定程度上塑造了互联网+社会。本书内容基于不同学科......一起来看看 《另一个地球》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

html转js在线工具
html转js在线工具

html转js在线工具