gpt4 book ai didi

f# - F# 中的应用程序架构/组合

转载 作者:行者123 更新时间:2023-12-03 05:22:07 25 4
gpt4 key购买 nike

最近我一直在 C# 中使用 SOLID 达到一个非常极端的水平,并且在某些时候意识到我现在除了编写函数之外基本上没有做其他事情。在我最近再次开始研究 F# 之后,我认为对于我现在所做的大部分工作来说,它可能是更合适的语言选择,所以我想尝试将真实世界的 C# 项目移植到 F#作为概念证明。我想我可以完成实际的代码(以一种非常不惯用的方式),但我无法想象一个架构会是什么样子,它允许我以与 C# 中类似的灵活方式工作。

我的意思是我有很多使用 IoC 容器组合的小类和接口(interface),我也经常使用装饰器和复合等模式。这导致(在我看来)非常灵活和可进化的整体架构,使我可以轻松地在应用程序的任何点替换或扩展功能。根据所需更改的大小,我可能只需要编写一个新的接口(interface)实现,在 IoC 注册中替换它并完成。即使变化更大,我也可以替换对象图的部分内容,而应用程序的其余部分就像以前一样。

现在有了 F#,我没有类和接口(interface)(我知道我可以,但我认为这不是我想要进行实际函数式编程的重点),我没有构造函数注入(inject),也没有 IoC容器。我知道我可以使用高阶函数来做装饰器模式之类的事情,但这似乎并没有给我提供与使用构造函数注入(inject)的类相同的灵活性和可维护性。

考虑这些 C# 类型:

public class Dings
{
public string Lol { get; set; }

public string Rofl { get; set; }
}

public interface IGetStuff
{
IEnumerable<Dings> For(Guid id);
}

public class AsdFilteringGetStuff : IGetStuff
{
private readonly IGetStuff _innerGetStuff;

public AsdFilteringGetStuff(IGetStuff innerGetStuff)
{
this._innerGetStuff = innerGetStuff;
}

public IEnumerable<Dings> For(Guid id)
{
return this._innerGetStuff.For(id).Where(d => d.Lol == "asd");
}
}

public class GeneratingGetStuff : IGetStuff
{
public IEnumerable<Dings> For(Guid id)
{
IEnumerable<Dings> dingse;

// somehow knows how to create correct dingse for the ID

return dingse;
}
}

我会告诉我的 IoC 容器解决 AsdFilteringGetStuffIGetStuffGeneratingGetStuff因为它自己对该接口(interface)的依赖。现在,如果我需要不同的过滤器或完全删除过滤器,我可能需要 IGetStuff 的相应实现然后只需更改 IoC 注册。只要界面保持不变,我就不需要碰东西 应用程序。 OCP 和 LSP,由 DIP 启用。

现在我在 F# 中做什么?

type Dings (lol, rofl) =
member x.Lol = lol
member x.Rofl = rofl

let GenerateDingse id =
// create list

let AsdFilteredDingse id =
GenerateDingse id |> List.filter (fun x -> x.Lol = "asd")

我喜欢这是多么少的代码,但我失去了灵活性。是的,我可以拨打 AsdFilteredDingseGenerateDingse在同一个地方,因为类型是相同的 - 但是我如何决定调用哪个而不在调用站点硬编码呢?此外,虽然这两个函数可以互换,但我现在无法替换 AsdFilteredDingse 中的生成器函数。无需更改此功能。这不是很好。

下一次尝试:

let GenerateDingse id =
// create list

let AsdFilteredDingse (generator : System.Guid -> Dings list) id =
generator id |> List.filter (fun x -> x.Lol = "asd")

现在我通过使 AsdFilteredDingse 成为一个高阶函数而具有可组合性,但这两个函数不再可以互换。再想一想,他们可能无论如何都不应该如此。

我还能做什么?我可以在 F# 项目的最后一个文件中模仿我的 C# SOLID 中的“组合根”概念。大多数文件只是函数的集合,然后我有某种“注册表”,它取代了 IoC 容器,最后我调用了一个函数来实际运行应用程序并使用“注册表”中的函数。在“注册表”中,我知道我需要一个类型为(Guid -> Dings 列表)的函数,我将其命名为 GetDingseForId .这就是我所说的,而不是之前定义的单个函数。

对于装饰器,定义将是

let GetDingseForId id = AsdFilteredDingse GenerateDingse

要删除过滤器,我会将其更改为

let GetDingseForId id = GenerateDingse

这样做的缺点(?)是所有使用其他函数的函数都必须是高阶函数,而我的“注册表”必须映射 全部 我使用的函数,因为之前定义的实际函数不能调用以后定义的任何函数,尤其是那些来自“注册表”的函数。我也可能会遇到“注册表”映射的循环依赖问题。

这是否有意义?您如何真正构建可维护和可演化的 F# 应用程序(更不用说可测试的)?

最佳答案

一旦您意识到面向对象的构造函数注入(inject)与函数式 非常接近,这就很容易了。部分功能申请 .

首先,我会写 Dings作为记录类型:

type Dings = { Lol : string; Rofl : string }

在 F# 中, IGetStuff接口(interface)可以简化为带有签名的单个函数
Guid -> seq<Dings>

使用此函数的客户端会将其作为参数:
let Client getStuff =
getStuff(Guid("055E7FF1-2919-4246-876E-1DA71980BE9C")) |> Seq.toList
Client 的签名功能是:
(Guid -> #seq<'b>) -> 'b list

如您所见,它以目标签名的函数作为输入,并返回一个列表。

发电机

生成器函数很容易编写:
let GenerateDingse id =
seq {
yield { Lol = "Ha!"; Rofl = "Ha ha ha!" }
yield { Lol = "Ho!"; Rofl = "Ho ho ho!" }
yield { Lol = "asd"; Rofl = "ASD" } }
GenerateDingse函数有这个签名:
'a -> seq<Dings>

这实际上比 Guid -> seq<Dings> 更通用,但这不是问题。如果您只想撰写 ClientGenerateDingse ,你可以像这样简单地使用它:
let result = Client GenerateDingse

这将返回所有三个 Ding来自 GenerateDingse 的值.

装修

原来的 Decorator 有点难,但也不是很多。通常,不是将装饰(内部)类型添加为构造函数参数,而是将其作为参数值添加到函数中:
let AdsFilteredDingse id s = s |> Seq.filter (fun d -> d.Lol = "asd")

此函数具有以下签名:
'a -> seq<Dings> -> seq<Dings>

这不是我们想要的,但很容易用 GenerateDingse 组合它:
let composed id = GenerateDingse id |> AdsFilteredDingse id
composed函数有签名
'a -> seq<Dings>

正是我们要找的!

您现在可以使用 Clientcomposed像这样:
let result = Client composed

只会返回 [{Lol = "asd"; Rofl = "ASD";}] .

您不必定义 composed功能第一;你也可以当场作曲:
let result = Client (fun id -> GenerateDingse id |> AdsFilteredDingse id)

这也返回 [{Lol = "asd"; Rofl = "ASD";}] .

替代装饰器

前面的例子效果很好,但并没有真正装饰一个类似的功能。这是一个替代方案:
let AdsFilteredDingse id f = f id |> Seq.filter (fun d -> d.Lol = "asd")

这个函数有签名:
'a -> ('a -> #seq<Dings>) -> seq<Dings>

如您所见, f参数是另一个具有相同签名的函数,因此它更类似于装饰器模式。您可以像这样编写它:
let composed id = GenerateDingse |> AdsFilteredDingse id

同样,您可以使用 Clientcomposed像这样:
let result = Client composed

或像这样内联:
let result = Client (fun id -> GenerateDingse |> AdsFilteredDingse id)

有关使用 F# 组合整个应用程序的更多示例和原则,请参阅 my on-line course on Functional architecture with F# .

有关面向对象原则以及它们如何映射到函数式编程的更多信息,请参阅 my blog post on the SOLID principles and how they apply to FP .

关于f# - F# 中的应用程序架构/组合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22263779/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com