背景:本着"program to an interface, not an implementation"和Haskell type classes的精神,作为一个编码实验,我正在考虑创建一个主要建立在接口和扩展方法组合基础上的API意味着什么。我有两条准则:“使用扩展方法编程接口”:它何时变得太过分?
只要有可能就避免类继承。接口应实现为
sealed class
es。
(这是出于两个原因:首先,因为子类化引发了一些关于如何在其派生类中指定和强制执行基类的契约的令人讨厌的问题;其次,这是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的''拉泽而不是一个人为的例子。 –
ChaosPandion
2012-07-11 21:42:19
好吧,你通过使'IRottenApple'扩展'IApple'违反了规则#1(虽然它只是声明了类的继承) – 2012-07-11 21:43:46
@John:我不这么认为。我相信* interface *继承避免了许多子类化的问题。 (例如:是否必须从派生类中调用重写的方法?当您不允许子类化时,该问题就会消失。) – stakx 2012-07-11 21:50:04