【发布时间】:2021-09-10 14:57:55
【问题描述】:
我是您的经典 OOP 开发人员。然而,自从我发现纯函数式编程语言后,我就一直对为什么很感兴趣,因为 OOP 似乎以合理的方式解决了大多数业务案例。
在我的软件开发经验中,我现在已经到了寻求更简洁和富有表现力的语言的地步。我通常用 C# 编写我的软件,但对于我的最新项目,我决定迈出一大步,使用 F# 构建业务服务。在这样做的过程中,我发现很难理解如何使用纯函数方法完成解耦。
情况是这样的。我有一个数据源,即 WooCommerce,但我不想将我的函数定义绑定到那个特定的数据源。
在 C# 中,我显然想要一个看起来像这样的服务
public record Category(string Name);
public interface ICategoryService
{
Task<IEnumerable<Category>> GetAllAsync();
}
// With a definition for the service that specifies WooCommerce
public class WcCategoryService : ICategoryService
{
private readonly WCRestEndpoint wcRest;
// WooCommerce specific dependencies
public WcCategoryService(WCRestEndpoint wcRest)
{
this.wcRest = wcRest;
}
public Task<IEnumerable<Category>> GetAllAsync()
{
// Call woocommerce REST and map the category to our domain category
}
}
现在,如果将来我决定需要一个新的商店来提供类别,我可以为该特定服务定义一个新的实现,替换注入的类型,并且不会因为这种变化而弄乱依赖项。
试图了解函数依赖方法是如何解决的,我遇到了这种情况(阅读“Domain Modeling made functional”),其中类型签名直接定义依赖关系,因此上面的 C# 等效项将变成高度耦合的定义
type Category = { Name: string }
type GetCategories =
WCRestEndpoint
-> Category list
突然间,如果我要更改类别的来源,我将不得不更改功能签名或提供要使用的新定义,这会影响应用程序,因此不是很健壮。
我很好奇的是我是否误解了一些基本的东西。
以我的 OOP 大脑,我所能想到的就是这样
type Category = { Name: string }
// No longer directly dependent on WCRestEndpoint
type GetCategories = unit -> Category list
// But the definition would require scoped inclusion of the dependency
// Also how does the configuration get passed in without having the core library be dependent on the Environment or a config in the assembly?
let rest = WCRestEndpoint(/* Config... */)
type getCategories: GetCategories =
fun () ->
let wcCategories = rest.GetCategories()
// Convert the result into a Category type
我环顾四周,没有找到任何关于如何使用纯功能方法处理更改的解释,这让我相信我误解了一些基本的东西。
如何在不将函数类型签名绑定到实现特定类型的情况下公开函数式 API?我是不是想错了?
【问题讨论】:
-
不是答案,但blog.ploeh.dk 上写了很多关于功能架构的文章。
-
顺便说一句,与函数式程序员交谈时要小心使用 category 一词。
标签: c# functional-programming f# decoupling