不同的处理器用于不同的 类 - 以功能方式

Different processors for different classes - in a functional manner

假设我想制作一个馅饼烘焙应用程序。它应该能够创建一个 苹果派 以及一个 草莓派 。但我希望以后可以选择添加更多不同的饼图类型。所以我创建了一个名为 IPie 的接口,如下所示:

interface IPie
{
    List<string> Ingredients { get; set; }
    IPieMaker Maker { get; set; }
}

interface IPieMaker
{
    Task MakePie();
}

然后我可以为每种馅饼实现 IPieMaker。例如。苹果派,像这样:

public AppliePieMaker : IPieMake 
{
    public Task MakePie()
    {
        // ...do something with ingredients, specific to the apple pie
    }
}

具体的实现是内置到具体的饼图中。例如,苹果派可能是这样的:

public class ApplePie : IPie
{
    List<string> Ingredients { get; set; } = new List<string> { "Apples", "Flour", "Eggs" };
    IPieMaker Maker { get; set; } = new ApplePieMaker();        
}

所以不管馅饼是什么类型,我总是确信它能做出来。像这样:

var pies = new List<IPie> 
{
    new ApplePie(),
    new StrawBerryPie(),
};

foreach (var pie in pies)
{
    pie.Maker.MakePie();
}

现在真正的问题来了:

以上所有都取决于制造商是 属性 蛋糕 class(IPie 的实现)。如果我想要一个更实用的方法,其中 PieMaker class 之外怎么办?所以我这样称呼它:

// From here, the pies are simple dumb data structures
var pies = new List<IPie> 
{
    new ApplePie(),
    new StrawBerryPie(),
};

foreach (var pie in pies)
{
    var maker = new PieMaker(pie);
    maker.MakePie();
}

如何让 PieMaker class 处理不同类型的馅饼?显然,它可以从一个巨大的 switch 语句开始,然后将调用路由到特定的馅饼制作者(ApplePieMaker、StrawberryPieMaker 等),但这不是一种不好的做法吗?你会如何在函数式编程中做到这一点?

(我知道我不是在进行实际的函数式编程,但我喜欢它的简单性,这就是为什么我对不太面向对象的方法感到好奇)。

在大多数面向对象语言中,打开抽象类型的子类型被认为是一种不好的做法的原因是,继承被设计成一个开放的可扩展点,任何人都可以在其中添加新的实现(在您的情况下)IPie,包括将您的代码作为库使用的第三方。

在函数式语言中,您通常会将不同种类的饼表示为求和类型。在 sum 类型的情况下进行模式匹配并不被认为是不好的做法,因为该类型表示只能通过修改类型来扩展的封闭层次结构。

遗憾的是,C# 尚不包含创建可模拟求和类型的封闭 class 层次结构的方法,但如果您实际上并未分发代码供第三方使用,则可以无论如何都可以使用类型切换。请注意,编译器将无法以与大多数函数式语言相同的方式向您发出有关 "non-exhaustive pattern matches" 的警告。

一个想法可能是为每个 Pie 设置一个特定的 PieMaker,并将实现 classes 绑定到它的名称,即对于 class ApplePie: IPie 必须有一个 class 名为 ApplePieMaker: IPiemaker<IPie>.

public interface IPie 
{
  List<string> Ingredients { get; set; }
}

public interface IPieMaker<in IPie>
{
  Make(IPie pie);
}

然后有一个调解器获取特定的饼图,寻找一个 SpecificPieMaker,创建一个实例并调用 Make 方法。

void Make(object pie)
{
  var makerType = Type.GetType($"{pie.GetType().FullName}Maker");
  var maker =  ActivatorUtilities.CreateInstance(ServiceProvider, makerType , new object[] {} );
  makerType.GetMethod("Make").Invoke( .....
}

注意:这段代码只展示了这个概念的一些核心思想。你可以在寻找"mediator pattern"时找到更多信息(我知道它并不完全相同,但你可以从中得到一些想法)。