19

我一直在寻找这个设计问题的简洁答案,但没有成功。我在“.NET Framework 设计指南”“C# 编程指南”中都找不到帮助。我基本上必须将模式公开为 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:注册是以这种方式完成的(假设MyDoSomethingextends AbstractDoSomething

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

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

MyFramework.AddDoSomething(new DoSomething());
4

5 回答 5

16

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

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

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

话虽这么说,如果您要采用使用委托的方法,我什至不会让用户创建一个类 - 只需使用如下方法:

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

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

于 2012-06-11T16:19:52.110 回答
7

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

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

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

  • DoSomething 可以被视为一个事件(OnDoingSomething)
  • DoSomething 是可选的(因此您将其默认为无操作委托)
于 2012-06-11T16:22:16.267 回答
2

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

于 2012-06-11T17:57:06.777 回答
1

正如大多数此类问题的典型情况一样,我会说“视情况而定”。:)

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

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

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

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

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

于 2012-06-11T16:23:47.917 回答
1

需要考虑的几件事:

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

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

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

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

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

于 2012-06-11T16:27:52.817 回答