2015-09-05 42 views
8

我試圖創建一個具有較低級別的庫,它知道它需要保存和加載數據時,某些命令被稱爲一個解決方案,但對保存和加載功能的實現將與平臺提供具體項目引用下級庫。函數式編程和依賴倒置:如何抽象存儲?

我有一些車型,如:

type User = { UserID: UserID 
       Situations: SituationID list } 

type Situation = { SituationID: SituationID } 

而我想做的就是能夠定義和調用功能,如:

do saveUser() 
let user = loadUser (UserID 57) 

有什麼辦法來定義這個乾淨地使用功能語言,最好避免可變狀態(反正這不應該是必須的)?

一種方式做到這一點可能是這個樣子:

type IStorage = { 
    saveUser: User->unit; 
    loadUser: UserID->User } 

module Storage = 
    // initialize save/load functions to "not yet implemented" 
    let mutable storage = { 
     saveUser = failwith "nyi"; 
     loadUser = failwith "nyi" } 

// ....elsewhere: 
do Storage.storage = { a real implementation of IStorage } 
do Storage.storage.saveUser() 
let user = Storage.storage.loadUser (UserID 57) 

而且有這個變化,但所有我能想到的那些涉及某種未初始化狀態的。 (在Xamarin中,還有DependencyService,但這本身就是我想避免的依賴項)。

有沒有什麼方法可以編寫調用尚未實現的存儲函數的代碼,然後實現它,沒有使用可變狀態?

(注:這個問題是不是存儲本身 - 這只是我使用的例如它是關於如何注入功能,而無需使用不必要的可變狀態。)

回答

15

其他的答案在這裏也許會教你關於如何在F#中實現IO monad,這當然是一種選擇。然而在F#中,我經常只是與其他函數構成函數。您無需爲此定義「接口」或任何特定類型。

Outside-In開發您的系統,並通過關注他們需要實現的行爲來定義您的高級功能。通過傳入依賴關係作爲參數使它們成爲高階函數

需要查詢數據存儲?通過一個loadUser參數。需要保存用戶嗎?傳遞一個saveUser論點:

let myHighLevelFunction loadUser saveUser (userId) = 
    let user = loadUser (UserId userId) 
    match user with 
    | Some u -> 
     let u' = doSomethingInterestingWith u 
     saveUser u' 
    | None ->() 

loadUser參數推斷爲User -> User option類型,並且作爲saveUserUser -> unit,因爲doSomethingInterestingWithUser -> User類型的函數。

您現在可以'執行'loadUsersaveUser通過編寫函數調用到較低級別的庫。

典型的反應我得到這個做法是:這會需要我的參數太多,以我的函數傳遞!

事實上,如果出現這種情況,考慮一下,如果不是一聞該函數試圖做太多。

由於在這個問題的標題中提到了Dependency Inversion Principle,所以我想指出SOLID principles如果全部應用在一起,效果最好。 Interface Segregation Principle表示接口應該儘可能小,並且不會比當每個「接口」是單個函數時小。

有關描述此技術的更詳細的文章,您可以閱讀我的Type-Driven Development article

1

您可以抽象接口IStorage後面的存儲。我認爲那是你的意圖。

type IStorage = 
    abstract member LoadUser : UserID -> User 
    abstract member SaveUser : User -> unit 

module Storage = 
    let noStorage = 
     { new IStorage with 
      member x.LoadUser _ -> failwith "not implemented" 
      member x.SaveUser _ -> failwith "not implemented" 
     } 

在程序的另一部分,您可以有多個存儲實現。

type MyStorage() = 
    interface IStorage with 
     member x.LoadUser uid -> ... 
     member x.SaveUser u -> ... 

而且在定義了所有類型後,您可以決定使用哪種類型。

let storageSystem = 
    if today.IsShinyDay 
    then MyStorage() :> IStorage 
    else Storage.noStorage 

let user = storageSystem.LoadUser userID