【问题标题】:Delegates (Lambda expressions) Vs Interfaces and abstract classes委托(Lambda 表达式)与接口和抽象类
【发布时间】:2012-06-14 13:50:49
【问题描述】:

我一直在寻找这个设计问题的简洁答案,但没有成功。我在".NET Framework design guidelines""C# programing guidelines" 都找不到帮助。 我基本上必须将模式公开为 API,以便用户可以像这样定义并将他们的算法集成到我的框架中:

1)

// This what I provide
public abstract class AbstractDoSomething{
   public abstract SomeThing DoSomething();
}

用户需要实现这个抽象类,他们必须实现DoSomething 方法(我可以从我的框架中调用并使用它)

2)

我发现这也可以通过使用委托来实现:

public sealed class DoSomething{
   public String Id;
   Func<SomeThing> DoSomething;
}

在这种情况下,用户只能这样使用DoSomething类:

DoSomething do = new DoSomething()
{
  Id="ThisIsMyID",
  DoSomething = (() => new Something())
}

问题

这两个选项中的哪个最适合作为 API 公开,易于使用且最重要的是易于理解

编辑

如果是 1 :注册是这样完成的(假设MyDoSomething扩展AbstractDoSomething

MyFramework.AddDoSomething("DoSomethingIdentifier", new MyDoSomething());

如果是 2 :注册是这样完成的:

MyFramework.AddDoSomething(new DoSomething());

【问题讨论】:

  • 用户如何在你的框架中注册 AbstractDoSomethings?
  • @dtb 请看我的编辑(也稍微修改了代码)

标签: c# api design-patterns


【解决方案1】:

这两个选项中的哪一个最适合作为 API 公开的简单、可用且最重要的是易于理解?

第一个在 OOP 方面更“传统”,可能对许多开发人员来说更容易理解。它还可以在允许用户管理对象的生命周期方面具有优势(即:您可以让类实现IDisposable 并在关闭时处理实例等),以及易于在未来版本中扩展一种不会破坏向后兼容性的方法,因为向基类添加虚拟成员不会破坏 API。最后,如果您想使用类似 MEF 之类的东西来自动组合它,它可以更简单地使用,这可以从用户的角度简化/删除“注册”过程(因为他们可以只创建子类,并将其放入文件夹,并自动发现/使用它)。

第二种方法更实用,在很多方面都更简单。这允许用户在实现您的 API 时对其现有代码的更改要少得多,因为他们只需要将必要的调用包装在带有闭包的 lambda 中,而不是创建新类型。

话虽如此,如果你打算使用委托的方法,我什至不会让用户创建一个类 - 只需使用如下方法:

MyFramework.AddOperation("ThisIsMyID", () => DoFoo());

在我看来,这使您更清楚地知道您正在直接向系统添加操作。它还完全消除了公共 API (DoSomething) 中对其他类型的需求,这再次简化了 API。

【讨论】:

  • +1。感谢您的回答。直接在我的框架中注册Func 的问题是我将来无法扩展该功能(无法向DoSomething 添加行为)
  • @GETah 不过,有一点需要注意 - 如果您使用抽象类,然后想向其中添加另一个操作,则必须将其设为虚拟类,而不是抽象类,或者你会破坏兼容性(这会带走使用类的优势)。
  • 您还需要考虑与现有 API 的一致性。一致性非常重要。在 API 中引入不能解决实际问题的新概念并不是一个好的做法。
  • @MBen 好点 - 虽然如果这是一个新的 API,那可能没关系。
  • @ReedCopsey 是的,当然,我的意思是你的框架中有更多现有的 API。如果有类似的模式,则应优先考虑以保持一致性。
【解决方案2】:

如果出现以下情况,我会选择抽象类/接口:

  • DoSomething 是必需的
  • DoSomething 通常会变得非常大(因此 DoSomething 的实现可以拆分为多个私有/受保护的方法)

如果出现以下情况,我会和代表一起去:

  • DoSomething 可以被视为一个事件 (OnDoingSomething)
  • DoSomething 是可选的(因此您将其默认为无操作委托)

【讨论】:

    【解决方案3】:

    对于大多数此类问题,我会说“视情况而定”。 :)

    但我认为使用抽象类而不是 lambda 的原因实际上归结为行为。通常,我认为 lambda 被用作回调类型的功能——您希望在发生其他事情时发生一些自定义的事情。我在客户端代码中经常这样做: - 拨打服务电话 - 取回一些数据 - 现在调用我的回调来相应地处理该数据

    您可以对 lambda 执行相同的操作 - 它们是特定的并且针对非常特定的情况。

    使用抽象类(或接口)实际上归结为你的类的行为是由它周围的环境驱动的。发生了什么,我在处理什么客户等?这些更大的问题可能表明您应该定义一组行为,然后允许您的开发人员(或 API 的消费者)根据他们的要求创建自己的行为集。当然,您也可以对 lambdas 做同样的事情,但我认为开发起来会更复杂,与用户进行清晰的沟通也会更复杂。

    所以,我想我粗略的经验法则是: - 使用 lambdas 进行特定的回调或副作用自定义行为; - 使用抽象类或接口为对象行为定制(或至少对象的大部分主要行为)提供一种机制。

    对不起,我不能给你一个明确的定义,但我希望这会有所帮助。祝你好运!

    【讨论】:

      【解决方案4】:

      需要考虑的几点:

      需要覆盖多少个不同的函数/委托?如果可能起作用,继承将以更易于理解的方式对“组”覆盖进行分组。如果您只有一个“注册”功能,但可以将许多子部分委托给实现者,这是“模板”模式的经典案例,最适合被继承。

      同一功能需要多少种不同的实现?如果只有一个,那么继承很好,但如果你有很多实现,委托可能会节省开销。

      如果有多个实现,程序是否需要在它们之间切换?或者它只会使用一个实现。如果需要切换,代表可能会更容易,但我会提醒这一点,尤其是取决于对#1 的回答。请参阅策略模式。

      如果覆盖需要访问任何受保护的成员,那么继承。如果它只能依赖于公众,那么委托。

      其他选择包括事件和扩展方法。

      【讨论】:

        【解决方案5】:

        虽然就个人而言,如果在我手中,我总是会选择委托模型。我只是喜欢高阶函数的简单和优雅。但是在实现模型时,要小心内存泄漏。订阅事件是 .Net 中内存泄漏的最常见原因之一。这意味着,假设如果您有一个暴露了一些事件的对象,那么在取消订阅所有事件之前,原始对象永远不会被释放,因为事件会创建一个强引用。

        【讨论】:

        • 感谢您的回答。不幸的是,制作 API 时的问题是您喜欢做的事情,而其他人喜欢做的事情是:p
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-02-19
        • 1970-01-01
        • 1970-01-01
        • 2012-08-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多