在 F# 中工作并实现“服务”模式时,例如围绕 Web API 或数据库或 USB 小工具的包装器,执行此操作的惯用方式是什么?我的倾向是使用例如IDatabase
和 Database
, ITwitter
和 Twitter
, ICamera
和 Camera
就像 C# 中的接口(interface)和类,允许简单的测试模拟。但如果这不是标准的方法,我不想编写“F# 中的 C#”。
我考虑使用 DU 来表示例如Camera | TestCamera
,但这意味着将所有模拟代码放入生产代码库,这听起来很可怕。但也许这只是我的 OO 背景。
我也考虑过制作IDatabase
功能记录。我仍然对这个想法持开放态度,但它似乎有点做作。此外,它排除了使用 IoC Controller 或任何“类似 MEF”的插件功能的想法(至少我知道)。
这样做的“惯用 F#”方式是什么?只遵循 C# 服务模式?
最佳答案
正如另一个答案中提到的,在 F# 中使用接口(interface)很好,这可能是解决问题的好方法。但是,如果您使用更多面向功能转换的编程风格,则可能不需要它们。
理想情况下,大多数实际有趣的代码应该是不执行任何效果的转换——因此它们不调用任何服务。相反,他们只是接受输入并产生输出。让我演示使用数据库。
使用 IDatabase
服务,你可以这样写:
let readAveragePrice (database:IDatabase) =
[ for p in database.GetProducts() do
if not p.IsDiscontinued then
yield p.Price ]
|> Seq.average
当这样写时,您可以提供
IDatabase
的模拟实现测试 readAveragePrice
中的平均逻辑是正确的。但是,您也可以像这样编写代码:let calculateAveragePrice (products:seq<Product>) =
[ for p in products do
if not p.IsDiscontinued then
yield p.Price ]
|> Seq.average
现在可以测试
calculateAveragePrice
没有任何 mock - 只需给它一些您想要处理的 sample 产品!这将数据访问从核心逻辑推到外部。所以你会有:database.GetProducts() |> calculateAveragePrice // Actual call in the system
[ Product(123.4) ] |> calculateAveragePrice // Call on sample data in the test
当然,这是一个简单的例子,但它显示了这个想法:
Push the data access code outside of the core logic and keep the core logic as pure functions that implement transformations. Then your core logic will be easy to test - given sample inputs, they should return the correct results!
关于f# - 在 F# 中实现 "services"的惯用方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32570410/