给定一个具有以下层的 ASP.NET MVC 应用程序:
- UI(视图、CSS、Javascript 等)
- 控制器
- 服务(包含业务逻辑和数据访问)
没有单独的数据访问层的原因是我使用的是 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
然后在我的控制器层中,我将命名另一个模块,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 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
使用接口,我们将有以下实现:
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
用法如下:
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
显然接口实现的代码要少得多,但它不再像函数式代码了。如果我开始在整个 Web 应用程序中使用接口,我什么时候知道何时使用高阶函数?- 否则我只会得到一个普通的旧 OOP 解决方案。
简而言之:什么时候应该使用接口,什么时候使用高阶函数?- 必须画一些线,否则都是类型和接口,FP 的美感就消失了。