【发布时间】:2012-07-11 21:38:45
【问题描述】:
背景: 本着"program to an interface, not an implementation" 和Haskell type classes 的精神,作为一个编码实验,我正在思考创建一个主要基于以下组合的API 意味着什么接口和扩展方法。我有两个指导方针:
尽可能避免类继承。接口应该被实现为
sealed classes。
(这有两个原因:首先,因为子类化提出了一些关于如何在其派生类中指定和执行基类契约的讨厌问题。第二,和这就是 Haskell 类型类的影响,多态性不需要子类化。)尽可能避免使用实例方法。如果可以使用扩展方法完成,则首选这些方法。
(这是为了帮助保持接口紧凑:通过其他实例方法的组合可以完成的所有事情都成为扩展方法。剩下的接口是核心功能,尤其是状态改变方法。)
问题:我对第二条准则有疑问。考虑一下:
interface IApple { }
static void Eat(this IApple apple)
{
Console.WriteLine("Yummy, that was good!");
}
interface IRottenApple : IApple { }
static void Eat(this IRottenApple apple)
{
Console.WriteLine("Eat it yourself, you disgusting human, you!");
}
sealed class RottenApple : IRottenApple { }
IApple apple = new RottenApple();
// API user might expect virtual dispatch to happen (as usual) when 'Eat' is called:
apple.Eat(); // ==> "Yummy, that was good!"
显然,对于预期的结果 ("Eat it yourself…"),Eat 应该是常规实例方法。
问题:关于使用扩展方法与(虚拟)实例方法的更精确/更准确的指南是什么?什么时候使用扩展方法来“编程到接口”走得太远了?在什么情况下实际需要实例方法?
我不知道是否有任何明确的通用规则,所以我不期待一个完美的、普遍的答案。对上述准则 (2) 的任何有充分理由的改进表示赞赏。
【问题讨论】:
-
我认为一个更好的起点是
IEnumerable<T>,而不是一个人为的例子。 -
好吧,你已经 kindof 违反了规则 #1,使
IRottenApple扩展IApple...(尽管它只是声明 class 继承) -
@John:我不这么认为。我相信 interface 继承避免了许多子类化问题。 (例如:是否必须从派生类调用重写的方法?当您禁止子类化时,这个问题就消失了。)
标签: c# interface extension-methods api-design