7

背景:本着“编程到接口,而不是实现”Haskell 类型类的精神,作为一个编码实验,我正在思考创建一个主要基于接口和扩展组合的 API 意味着什么方法。我有两个指导方针:

  1. 尽可能避免类继承。接口应该实现为sealed classes。
    (这有两个原因:首先,因为子类化引发了一些令人讨厌的问题,即如何在其派生类中指定和执行基类的契约。其次,这就是 Haskell 类型类的影响,多态性不需要子类化。)

  2. 尽可能避免使用实例方法。如果可以使用扩展方法完成,则首选这些方法。
    (这旨在帮助保持接口紧凑:可以通过组合其他实例方法完成的所有操作都成为扩展方法。接口中保留的是核心功能,尤其是状态更改方法。)

问题:我对第二条指南有疑问。考虑一下:

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) 的任何有充分理由的改进都值得赞赏。

4

2 回答 2

6

您的指导方针已经足够好:它已经说“尽可能”。因此,任务实际上是在更多细节中拼出“尽可能”位。

我使用这种简单的二分法:如果添加方法的目的是隐藏子类之间的差异,则使用扩展方法;如果目的是突出差异,请使用虚拟方法。

Eat的方法是引入子类之间差异的方法的示例:吃(或不吃)苹果的过程取决于它是哪种苹果。因此,您应该将其实现为实例方法。

尝试隐藏差异的方法示例如下ThrowAway

public static void ThrowAway(this IApple apple) {
    var theBin = RecycleBins.FindCompostBin();
    if (theBin != null) {
        theBin.Accept(apple);
        return;
    }
    apple.CutUp();
    RecycleBins.FindGarbage().Accept(apple);
}

如果扔掉苹果的过程是相同的,不管苹果的种类如何,那么该操作是在扩展方法中实现的主要候选者。

于 2012-07-11T22:00:19.863 回答
1

对我来说,预期的输出是正确的。您将变量类型转换为(可能使用错误)作为 IApple。

例如:

IApple apple = new RottenApple();
apple.Eat();  // "Yummy, that was good!"
IRottenApple apple2 = new RottenApple();
apple2.Eat(); // "Eat it yourself, you disgusting human, you!"
var apple3 = new RottenApple();
apple.Eat();  // "Eat it yourself, you disgusting human, you!"

问题:关于使用扩展方法与(虚拟)实例方法的改进/更准确的指导方针是什么?什么时候使用扩展方法来“编程到接口”走得更远?在什么情况下实际需要实例方法?

在开发应用程序时只是我的个人意见:

当我编写我可能或其他人可能使用的东西时,我会使用实例方法。这是因为它是对实际类型的要求。FlyingObject考虑一个带有方法的接口/类Fly()。这是飞行物体的基本基本方法。创建扩展方法真的没有意义。

我使用(很多)扩展方法,但这些从来都不是使用它们扩展的类的要求。例如,我有int创建一个扩展方法SqlParameter(另外它是内部的)。将该方法作为 int 基类的一部分仍然没有任何意义,它实际上与 int 是什么或做什么无关。扩展方法在视觉上是创建使用类/结构的可重用方法的好方法。

于 2012-07-11T22:09:06.123 回答