2015-09-05 37 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