Dependency injection 何时使用接口,何时使用高阶函数?
给定具有以下层的ASP.NET MVC应用程序:Dependency injection 何时使用接口,何时使用高阶函数?,dependency-injection,f#,functional-programming,Dependency Injection,F#,Functional Programming,给定具有以下层的ASP.NET MVC应用程序: 用户界面(视图、CSS、Javascript等) 控制器 服务(包含业务逻辑和数据访问) 之所以没有单独的数据访问层,是因为我使用的是SQL类型提供程序 (以下代码可能不起作用,因为它只是一个草稿)。 现在想象一个名为UserService的服务,其定义如下: module UserService = let getAll memoize f = memoize(fun _ -> f) let tryG
- 用户界面(视图、CSS、Javascript等)
- 控制器
- 服务(包含业务逻辑和数据访问)
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
然后在我的控制器层中,我将有另一个名为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
使用接口,我们将实现以下功能:
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
显然,接口实现的代码要少得多,但它不再像功能代码了。如果我开始在我的web应用程序中使用接口,我什么时候知道什么时候应该改用高阶函数否则,我将得到一个简单的面向对象解决方案
简而言之:什么时候应该使用接口,什么时候应该使用高阶函数必须画一些线,否则所有的线都是类型和接口,FP的美就消失了。关于接口。首先,我认为您可以将接口看作是命名的函数对。如果您有:
type ICacheProvider =
abstract Get : string -> option<obj>
abstract Set : string * obj -> unit
…然后定义一个类型来包装它并添加缓存(这将由web应用程序的主类型使用-它与您在示例中定义的类型非常相似,但现在我们使用缓存提供程序分离数据库访问和存储):
总结。但是-我认为您使用采用
ICacheProvider
的类的方法非常好-F#在混合函数和面向对象风格方面非常好。我发布的示例实际上只是一个可能的扩展,在更大的项目中可能会有用(如果您想使用功能方面并明确区分功能的不同方面)我在我的博客上写了一些想法,试图澄清对象和高阶函数之间的关系,除此之外:单元测试框架也倾向于更好地模拟接口,而不是模拟函数签名的元组。
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
type ICacheProvider =
abstract Get : string -> option<obj>
abstract Set : string * obj -> unit
type CacheProvider = (string -> option<obj>) * (string * obj -> unit)
module UserService =
let getAll () = (...)
let tryGetByID id = (...)
let add name = (...)
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