【问题标题】:Different processors for different classes - in a functional manner不同类别的不同处理器 - 以功能方式
【发布时间】:2020-08-26 15:50:22
【问题描述】:

假设我想做一个馅饼烘焙应用程序。它应该能够创建一个苹果派以及一个草莓派。但我希望以后可以选择添加更多不同的馅饼类型。于是我做了一个叫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();
}

现在真正的问题来了:

以上所有都取决于 maker 是 pie 类的属性(IPie 的实现)。如果我想要一个更实用的方法,其中PieMaker 在类之外?所以我这样称呼它:

// 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 类处理不同类型的饼图?显然,它可以从一个巨大的 switch 语句开始,然后将调用路由到特定的 pie 制造商(ApplePieMaker、StrawberryPieMaker 等),但这不是一个不好的做法吗?您将如何在函数式编程中做到这一点?

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

【问题讨论】:

    标签: c# interface open-closed-principle


    【解决方案1】:

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

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

    不幸的是,C# 尚不包含创建可模拟 sum 类型的封闭类层次结构的方法,但如果您实际上并未分发代码以供第三方使用,则可以使用类型切换反正。请注意,编译器无法像在大多数函数式语言中那样警告您“非详尽模式匹配”。

    【讨论】:

      【解决方案2】:

      一个想法可能是为每个 Pie 设置一个特定的 PieMaker,并将实现类绑定到它的名称,即对于 ApplePie: IPie 类,必须有一个名为 ApplePieMaker: IPiemaker&lt;IPie&gt; 的类。

      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( .....
      }
      

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

      【讨论】:

      • 好主意。事实上,Jimmy Bogard 有一个很棒的库可以做到这一点:github.com/jbogard/MediatR
      • 不是必须是var makerType = Type.GetType($"{pie,GetType().FullName}Maker");吗?
      • 不错的解决方案! ? 一点点改进:由于所有的“制造者”都实现了IPieMaker接口,所以可以将激活器的结果强制转换为IPieMaker,然后无需使用反射来调用Make方法,可以调用直接?
      • 是的,你是对的。这是因为在我的生产代码中,我还实现了一个通用响应类型 IRequest&lt;out IResponse&gt;(在我们的例子中是 IPie)。我发现除了使用反射之外没有其他调用通用接口方法的方法。但也许还有其他解决方案?
      猜你喜欢
      • 1970-01-01
      • 2017-03-14
      • 2018-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-10
      • 1970-01-01
      • 2015-10-22
      相关资源
      最近更新 更多