惯用的实现方法";服务“;在F#
在F#中工作并实现“服务”模式时,例如围绕web API或数据库或USB小工具的包装器,惯用的方法是什么?我倾向于像C#一样使用例如惯用的实现方法";服务“;在F#,f#,F#,在F#中工作并实现“服务”模式时,例如围绕web API或数据库或USB小工具的包装器,惯用的方法是什么?我倾向于像C#一样使用例如IDatabase和数据库,ITwitter和Twitter,ICamera和Camera接口和类,以便进行简单的模拟测试。但如果这不是标准的方法,我不想把“C”写成F 我考虑过使用DU来表示,例如,Camera | TestCamera,但这意味着将所有模拟代码放入生产代码库,这听起来很可怕。但也许这只是我的OO背景 我还考虑将IDatabase作为函数的记录。我
IDatabase
和数据库
,ITwitter
和Twitter
,ICamera
和Camera
接口和类,以便进行简单的模拟测试。但如果这不是标准的方法,我不想把“C”写成F
我考虑过使用DU来表示,例如,Camera | TestCamera
,但这意味着将所有模拟代码放入生产代码库,这听起来很可怕。但也许这只是我的OO背景
我还考虑将IDatabase
作为函数的记录。我仍然对这个想法持开放态度,但它似乎有点做作。此外,它还排除了使用IoC控制器或任何“类似MEF”的插件功能的想法(至少我知道)
“惯用的F#”方法是什么?只需遵循C#服务模式?在F#中使用接口没有什么错 教科书式的FP方法是让客户机将实现组件逻辑的函数作为参数,但这种方法不能随着这些函数数量的增加而很好地扩展。将它们组合在一起是一个好主意,因为这样可以提高可读性,在.NET中使用接口是一种惯用的方法。特别是在定义良好的组件的边界上,它是有意义的,我认为您引用的三个接口非常适合这种描述 我已经看到DU的使用方式与您描述的大致相同(提供了生产和假实现),但它也不适合我
如果有什么区别的话,函数的记录不是惯用的。在FP语言中,它们是一种穷人将行为捆绑在一起的方式,但如果有一件事面向对象语言做得对的话,那就是将行为捆绑在一起。接口只是一个更好的工具,尤其是因为F#允许您创建与对象表达式语法内联的实现。正如另一个答案中提到的,在F#中使用接口是很好的,这可能是解决问题的一个好方法。然而,如果您使用更面向函数转换的编程风格,您可能不需要它们 理想情况下,大多数实际感兴趣的代码应该是不执行任何效果的转换,因此它们不会调用任何服务。相反,他们只接受输入并产生输出。让我演示如何使用数据库 使用
IDatabase
服务,您可以编写如下内容:
let readAveragePrice (database:IDatabase) =
[ for p in database.GetProducts() do
if not p.IsDiscontinued then
yield p.Price ]
|> Seq.average
let calculateAveragePrice (products:seq<Product>) =
[ for p in products do
if not p.IsDiscontinued then
yield p.Price ]
|> Seq.average
这样编写时,可以提供IDatabase
的模拟实现,以测试readAveragePrice
中的平均逻辑是否正确。但是,您也可以这样编写代码:
let readAveragePrice (database:IDatabase) =
[ for p in database.GetProducts() do
if not p.IsDiscontinued then
yield p.Price ]
|> Seq.average
let calculateAveragePrice (products:seq<Product>) =
[ for p in products do
if not p.IsDiscontinued then
yield p.Price ]
|> Seq.average
当然,这是一个过于简单的例子,但它表明了以下观点:
将数据访问代码推到核心逻辑之外,并将核心逻辑保持为实现转换的纯函数。然后您的核心逻辑将很容易测试-给定样本输入,它们将返回正确的结果
这里的其他人写道,在F.*中使用接口没有什么错,我认为接口只不过是一种互操作机制,它可以使F.*与其他.NET语言编写的代码一起工作。
当我用C#编写代码时,我遵循以下步骤。当一起使用时,和最终应为 因此,即使在C#中,正确设计的接口也应该只有一种方法:public interface IProductQuery
{
IEnumerable<Product> GetProducts(int categoryId);
}
在本例中,您会注意到,getProducts
作为参数传递给calculatearageprice
函数,其类型甚至没有声明。这非常符合让客户机定义接口的原则:参数的类型是从客户机的用法推断出来的。它的类型为'a->#seq
(因为categoryId
可以是任何泛型类型'a
)
像
calculatearageprice
这样的函数是一个函数,这是一个功能性的事情。几乎是重复的:我看到了推理和逻辑,但对结论有困难。给定的记录是一堆get\uu
方法,这是否意味着它们的设计也很糟糕?在哪里/为什么画这条线?不过,这可能是一个较长的对话。具体来说,以ICamera
为例,当前getNewPhoto-camera-config
必须调用几个camera.setwhater-cfg.whater
,camera.snap()
,camera.waitfile
,以及camera.download
。将这些方法作为N个单独的参数传递给getNewPhoto
,似乎很麻烦。您建议如何处理这样的事情?(注意,getNewPhoto
函数在许多不同的地方使用,因此我不认为将其分解为更少的参数块并将其到处内联是明智的。)正如我在文章中所写的,如果您需要将所有这些函数作为参数传递给同一个函数,这可能是该函数做得太多的一个迹象。当你把SRP和ISP结合起来时,这也就有了一个实值。“记录是GETZ方法的捆绑包”,只有在考虑IL级实现时才是真的。记录是一组值,而不是方法。那些get
方法本来可以作为字段来实现,这不会有什么区别;其他答案是基于这一点的细节。