> UI(Views,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
显然,接口实现的代码要少得多,但是它不再像功能代码了.如果我开始在我的网络应用程序中使用界面,我何时才知道何时使用更高级的功能? – 否则我最终会得到一个简单的老OOP解决方案.
所以简而言之:什么时候应该使用接口,什么时候使用高阶函数? – 必须绘制一些线条,或者将它们全部作为类型和界面,FP的美丽消失.
type ICacheProvider = abstract Get : string -> option<obj> abstract Set : string * obj -> unit
那么这几乎相当于有一个(或一个记录)的功能:
type CacheProvider = (string -> option<obj>) * (string * obj -> unit)
使用接口的好处是你给类型一个名字(你会得到记录也),你更清楚地表达你的意图(其他组件可以实现接口).
我认为使用界面是一个好主意,如果你有两个以上的函数通常传递给一些其他的功能在一起 – 这样你就避免了太多的参数.
模块或类.你的代码的真正区别在于是使用具有高阶函数的模块还是使用接口作为构造函数参数的类. F#是一个结合功能和OO风格的多范式语言,所以我认为以这种方式使用类是完美的. (在定义数据类型以表示域等时,仍然可以从功能样式中受益)
要记住的一件事是功能性编程就是组合.这在这种情况下可能不是有用的,但是我总是喜欢编写可以组合的代码来添加更多的功能,而不是需要我在使用它时提供某些东西的代码.
也许您可以编写它,以便您的数据库访问代码不直接进行缓存(这将包括所有数据库查询和预处理逻辑):
module UserService = let getAll () = (...) let tryGetByID id = (...) let add name = (...)
…然后定义一个包装此类型并添加缓存的类型(然后这将被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
概要.但是 – 我认为你使用一个使用ICacheProvider的类的方法是完美的 – F#在混合功能和面向对象的风格方面是非常好的.我发布的例子真的只是一个可能的扩展,可能在更大的项目中有用(如果你想使用功能方面,并明确区分功能的不同方面)