ebb*_*ebb 12 f# functional-programming dependency-injection
给定具有以下层的ASP.NET MVC应用程序:
没有单独的数据访问层的原因是我正在使用SQL类型提供程序.
(以下代码可能无法正常工作,因为它只是一个原始草案).现在假设一个名为的服务UserService
定义如下:
module UserService =
let getAll memoize f =
memoize(fun _ -> f)
let tryGetByID id f memoize =
memoize(fun _ -> f id)
let add evict f name keyToEvict =
let result = f name
evict keyToEvict
result
Run Code Online (Sandbox Code Playgroud)
然后在我的控制器层中,我将有另一个名为的模块,UserImpl
或者它也可以命名为UserMemCache
:
module UserImpl =
let keyFor = MemCache.keyFor
let inline memoize args =
MemCache.keyForCurrent args
|> CacheHelpers.memoize0 MemCache.tryGet MemCache.store
let getAll = memoize [] |> UserService.getAll
let tryGetByID id = memoize [id] |> UserService.tryGetByID id
let add =
keyFor <@ getAll @> [id]
|> UserService.add MemCache.evict
Run Code Online (Sandbox Code Playgroud)
使用方法如下:
type UserController() =
inherit Controller()
let ctx = dbSchema.GetDataContext()
member x.GetAll() = UserImpl.getAll ctx.Users
member x.UserNumberOne = UserImpl.tryGetByID ctx.Users 1
member x.UserNumberTwo = UserImpl.tryGetByID ctx.Users 2
member x.Add(name) = UserImpl.add ctx.Users name
Run Code Online (Sandbox Code Playgroud)
使用接口,我们将具有以下实现:
type UserService(ICacheProvider cacheProvider, ITable<User> db) =
member x.GetAll() =
cacheProvider.memoize(fun _ -> db |> List.ofSeq)
member x.TryGetByID id =
cacheProvider.memoize(fun _ -> db |> Query.tryFirst <@ fun z -> z.ID = ID @>)
member x.Add name =
let result = db.Add name
cacheProvider.evict <@ x.GetAll() @> []
result
Run Code Online (Sandbox Code Playgroud)
用法如下:
type UserController(ICacheProvider cacheProvider) =
inherit Controller()
let ctx = dbSchema.GetDataContext()
let userService = new UserService(cacheProvider, ctx.Users)
member x.GetAll() = userService.GetAll()
member x.UserNumberOne = userService.TryGetByID 1
member x.UserNumberTwo = userService.TryGetByID 2
Run Code Online (Sandbox Code Playgroud)
显然,接口实现的代码要少得多,但它实际上并不像功能代码那样.如果我开始在我的网络应用程序中使用界面,我何时知道何时使用更高阶的功能呢? - 否则我最终会得到一个简单的旧OOP解决方案.
简而言之:什么时候应该使用接口,何时使用更高阶的功能? - 必须绘制一些线条,或者它们都是类型和界面,FP的美丽消失了.
Tom*_*cek 10
在接口上.首先,我认为您可以将接口视为命名的函数对.如果你有:
type ICacheProvider =
abstract Get : string -> option<obj>
abstract Set : string * obj -> unit
Run Code Online (Sandbox Code Playgroud)
那么这几乎等同于拥有一对(或记录)函数:
type CacheProvider = (string -> option<obj>) * (string * obj -> unit)
Run Code Online (Sandbox Code Playgroud)
使用接口的好处是,您可以为类型指定一个名称(您也可以通过记录获得该名称),并且您可以更清楚地表达您的意图(其他组件可以实现该接口).
我认为使用接口是个好主意,如果你有两个以上的函数经常一起传递给其他函数 - 这样你就避免了太多的参数.
模块或类.代码的真正区别在于是使用具有高阶函数的模块还是将接口作为构造函数参数的类.F#是一种结合了功能和OO风格的多范式语言,所以我认为以这种方式使用类是完全没问题的.(在定义表示域等的数据类型时,您仍然可以从功能样式中受益)
要记住的一件事是功能编程完全是关于构图.在这种情况下,这可能没那么有用,但我总是喜欢编写可以编写的代码来添加更多功能,而不是需要我在需要时提供某些内容的代码.
也许您可以编写它,以便您的数据库访问代码不直接进行缓存(这将包括所有数据库查询和预处理逻辑):
module UserService =
let getAll () = (...)
let tryGetByID id = (...)
let add name = (...)
Run Code Online (Sandbox Code Playgroud)
...然后定义一个包装它的类型并添加缓存(然后这将由Web应用程序的主要类型使用 - 它与您在示例中定义的类型非常相似,但现在我们将数据库分开使用缓存提供程序访问和记忆):
type UserService(cacheProvider:ICacheProvider) =
member x.GetAll() = cacheProvider.memoize UserSerivce.getAll ()
member x.TryGetByID id = cacheProvider.memoize UserService.tryGetByID id
member x.Add name = cacheProvider.memoize UserService.add name
Run Code Online (Sandbox Code Playgroud)
摘要.但是 - 我认为你使用一个类的方法ICacheProvider
非常好 - F#在混合功能和面向对象的风格方面相当不错.我发布的示例实际上只是一个可能的扩展,可能在更大的项目中有用(如果您想使用功能方面并明确区分功能的不同方面)
归档时间: |
|
查看次数: |
660 次 |
最近记录: |