背景:本着“编程到接口,而不是实现”和Haskell 类型类的精神,作为一个编码实验,我正在思考创建一个主要基于接口和扩展组合的 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) 的任何有充分理由的改进都值得赞赏。