如何使用 F# 實現 Strategy Pattern ?

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

内容简介:Strategy Pattern 是 OOP 中最著名的 Design Pattern,幾乎可以說是 OOP 中 『解耦合』最經典的應用,F# 既然是 Function First Language,就讓我們以 function 的角度重新思考什麼是 『解耦合』。macOS High Sierra 10.13.3.NET Core SDK 2.1.101

Strategy Pattern 是 OOP 中最著名的 Design Pattern,幾乎可以說是 OOP 中 『解耦合』最經典的應用,F# 既然是 Function First Language,就讓我們以 function 的角度重新思考什麼是 『解耦合』。

Version

macOS High Sierra 10.13.3

.NET Core SDK 2.1.101

JetBrains Rider 2017.3.1

F# 4.1

User Story

假設你在處理訂單,訂單的折扣方式有兩種

  • 超過 1000 元,則 滿千送百
  • 不到 1000 元,則 全館8折

Task

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

Definition

Strategy Pattern

將不同演算法抽象化成相同 interface,讓高階模組與實際演算法解耦合,而彼此僅相依於 interface,進而可動態切換演算法

如何使用 F# 實現 Strategy Pattern ?

首先思考 Strategy Pattern 的本質:

  1. Strategy 必須與 Context 解耦合
  2. Strategy 必須能動態切換

只要能達到這兩個目標,就算完成了 Strategy Pattern。

OOP 思考方式

OOP 強調是 data 與 function 合一,認為什麼都是物件,所以 strategy 也是物件。

要將不同的 strategy 抽象化 看成相同的物件,才能使用 多型 操作,所以要設計 interface 訂定 抽象化 的標準。

也就是 OOP 是將焦點放在 不同的部分 ,進而將 不同的部分 抽象化成 Inteface。

FP 思考方式

FP 強調是 data 與 function 分開,data 有 Type System,function 有 Higher Order Function、Funciton Composition,因為 strategy 只是功能,所以是 function。

要將相同的部分抽出為 Higher Order Function,不同 strategy 抽成獨立 funciton,再將 strategy 以參數的方式傳入 Higher Order Function。

也就是 FP 是將焦點放在 相同的部分 ,進而將 相同的部分 抽成 Higher Order Function。

Implemetation

Strategy.fs

namespace OrderLibrary

module PriceStrategy =
    let rebateStrategy price = price - 100.0
    let discountStrategy discount price = price * discount * 1.0

買千送百rebateStrategy() 表示。

全館8折discountStrategy() 表示。

特別注意 rebateStrategy()discountStrategy() 的 singnature 並不一樣,傳統 OOP 在使用 Strategy Pattern 時,必須先定義 strategy interface,但只要遇到 strategy 間 signature 不同時就很困擾,甚至要動用 Adapter Pattern。

但因為 FP 沒有 interface 概念,所以不需要為不同的 strategy 抽象化 成相同 interface,故也沒有 interface 很難開的困擾。

OrderService.fs

namespace OrderLibrary

module OrderService =
    let strategyFactory price = 
        match (price > 1000.0) with
        | true  -> PriceStrategy.rebateStrategy
        | false -> PriceStrategy.discountStrategy 0.8
    
    let getPrice price =
        price
        |> strategyFactory price

第 4 行

let strategyFactory price = 
    match (price > 1000.0) with
    | true  -> PriceStrategy.rebateStrategy
    | false -> PriceStrategy.discountStrategy 0.8

既然有不同的 strategy,就會有選擇 strategy 的邏輯,所以 factory 少不了,只是從 OOP 的 factory class 退化成 factory function。

Strategy 的選擇,使用 FP 的 Pattern Matching 最適合,根據不同的條件回傳不同的 function。

如何使用 F# 實現 Strategy Pattern ?

F# 雖然不用寫型別,但對於型別檢查依然非常嚴格, strategyFactory() 被推導為 float -> (float -> float) ,也就是我們必須回傳一個 float -> float 的 function。

在 C# 我們必須明確使用 delegateFunc<float, float> 定義 strategy 的 signature,但在 F# 都省了,因此程式碼變得非常精簡,兼具強型別語言與弱型別語言的優點。

rebateStrategy()discountStrategy() 的 signature 畢竟不一樣,因此在使用 Pattern Matching 時,必須先將 float -> float 整理好,因爲 F# 支援 Currying, discountStrategy 0.8 會自動回傳 float -> float 的 function,如此將符合 Pattern Matching 對型別的要求。

FP 不用 interface,不代表沒有型別要求,透過 Currying,可以解決 OOP 因為需求不同難開 interface 的老問題,只要在最後 signature 一樣即可,並不要求設計 function 時都要有相同的 signature,可使用 Currying 逐步完成 signature 要求

第 9 行

let getPrice price =
    price
    |> strategyFactory price

將 price 以 Pipeline 方式傳給對的 strategy 計算,其中 strategyFactory price 將傳回對的 strategy。

Program.fs

open System
open OrderLibrary

[<EntryPoint>]
let main argv =
    1200.0
    |> OrderService.getPrice
    |> printfn "%f"

    800.0
    |> OrderService.getPrice
    |> printfn "%f"
    
    0 // return an integer exit code

將各種 price 以 Pipeline 方式傳給 OrderService.getPrice() 計算,並將結果傳給 printfn() 顯示。

Retrospective

回想 Strategy Pattern 的本質:

  1. Strategy 必須與 Context 解耦合
  2. Strategy 必須能動態切換

FP 雖然沒有定義 interface,但 strategy 已經與 context 實質解耦合,strategy 都必須嚴格遵守 float -> float 的 signature,任何 float -> float 的 function 都可視為 strategy。

只要遵守 float -> float 的 strategy,就能被 Pattern Matching 動態切換。

所以 FP 版的 Strategy Pattern 雖然沒有 interface 也沒有 多型 ,但本質與 OOP 是相同的。

Conclusion

  • 不用很糾結一定要使用 interface 與 多型 ,重點在於 解耦合動態切換 ,FP 使用 Type Inference 與 Higher Order Function 也能達成相同的目標
  • FP 的 Higher Order Function,幾乎可以解決原本 OOP Design Pattern 想解決的很多問題,FP 設計時要將焦點放在 相同的部分 ,抽出 Higher Order Function 後,再透過 Function Composition 組合成新的 function

Sample Code

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


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

查看所有标签

猜你喜欢:

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

C++ 程序设计语言(特别版)(英文影印版)

C++ 程序设计语言(特别版)(英文影印版)

[美] Bjarne Stroustrup / 高等教育出版社 / 2001-8-1 / 55.00

《C++程序设计语言》(特别版)(影印版)作者是C++的发明人,对C++语言有着全面、深入的理解,因此他强调应将语言作为设计与编程的工具,而不仅仅是语言本身,强调只有对语言功能有了深入了解之后才能真正掌握它。《C++程序设计语言》编写的目的就是帮助读者了解C++是如何支持编程技术的,使读者能从中获得新的理解,从而成为一名优秀的编程人员和设计人员。一起来看看 《C++ 程序设计语言(特别版)(英文影印版)》 这本书的介绍吧!

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

在线图片转Base64编码工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具