C#使用接口模拟多重继承有什么好处?

C# What is the advantage of using interfaces for simulating multiple inheritance?

接口是 C# 的一项功能,我一直无法完全理解其用途。我看到它们在专业代码中一直被使用,但对于我来说,我无法理解其中的原因。

我这里的问题具体是关于模拟多重继承的接口。在阅读这个主题时,我经常会想到这样的例子:

interface IAddition
{
    int add(int a, int b);
}

interface ISubtraction
{
    int sub(int a, int b);
}

class Calculation : IAddition, ISubtraction
{        
    public int add(int a, int b)
    {
        return result1 = a + b;
    }

    public int sub(int a, int b)
    {
        return result1 = a - b;
    }
}

我不明白的是 - 这些接口在这里带来了什么好处?在我看来,您可以完全删除它们,并且计算 class 仍然可以完全相同。我在这里错过了什么?

如有任何帮助,我们将不胜感激 - 我一直在寻找可以在脑海中制作接口的东西 'click' 一段时间了。

值得注意的是,我是一名游戏开发人员,我正在为此寻找解决方案以提高我开发游戏的能力,但我认为这是一个足够笼统的问题 post .

See this msdn article for details ("No Multiple-Inheritance in .NET" section):

接口解决了多重继承的问题,这是不允许的,以防止编译器在试图找到正确的实现时出现歧义。在他们的示例中,BabyBasset 继承了 Hound 和 Puppy,但是如果说这两个 classes 都提供了一个名为 "Bark()" 的方法的实现,那么将执行哪个?因此模棱两可。

类 可以实现多个接口,如果有歧义(即两个方法在不同的接口中同名),它们可以在 class 中显式实现,从而消除歧义。

接口是对象将具有提供特定功能的某些成员的契约。

这允许您提供一定程度的抽象,以便编写代码以使用接口而不是 classes。

例如,我可能有一个 class 继承了 IList 接口。可以有任意数量的实现只存储 'things' 的列表。在幕后,实现是什么对我来说并不重要,我可能关心的是它们包含 Add()GetEnumerator() 以便我可以添加内容并循环遍历它们。由 class 继承接口的实现者提供实现。

通常会进行多重继承,这样您就可以拆分功能。您可能有一个 class 可以做减法、加法或两者兼而有之。在继承添加接口的对象上调用方法的代码只关心提供添加功能的成员以外的任何内容。与使用继承减法接口的 class 的代码相同。然后它允许您或其他人用其他东西替换那些 classes,只要他们实现消费者不关心的接口。

接口基本上是您和您的客户之间的契约,就像强制执行一样,它强制您的客户必须实施接口中声明的方法。

让我们以您的代码为例。

interface IAddition
{
    int add(int a, int b);
}

interface ISubtraction
{
    int sub(int a, int b);
}

class Calculation : IAddition, ISubtraction
{

}

所以如果你不实现 AddSub 方法,编译器会报错。如果你删除你的接口,就没有合同,所以没有人会检查你的 class 实现。你想做什么就做什么。

允许多重继承,在多种场景下有很好的意义。你 class 可以与其他接口有多个合同。例如你有 1 个接口,10 个不同的接口正在实现这个接口。所以一些 class 可以实现最顶层的接口,其他一些可以实现 1,2,5 接口,具体取决于它们各自的功能。

接口是一种具有约束力的合同,可保证您的 classes 将实现某些功能。 class 使用的接口越多,宣传使用的功能就越多。

想想像麦片盒标签这样的界面。如果它显示 "no sugar added",您可以相当确定您的麦片(或您的 class)没有添加任何糖分。

在您的示例中,IAddISub 宣传您 class 知道如何执行某些操作。由您决定您希望 class 的功能在一个或多个界面中拆分到何种细粒度级别。这一切都取决于未来的使用。如果您有一个仅供一个人使用的接口 class,那么可能并不真正需要该接口。

是的,你是对的。删除接口后,代码仍会编译。那么为什么要添加它们呢?

好吧,您的 IAdditionISubtraction 示例并不能很好地说明我的观点。由于学习编程的最佳方法是查看其他人的编码方式,因此我将使用 System.IComparable<T> 接口作为示例。它是.NET框架提供的接口。

我们之所以使用接口是为了实现多态性。接口就像 classes 必须遵守的协议。如果 class 实现了一个接口,那么可以保证 class 可以做接口指定的事情。这一切听起来很令人困惑,所以让我们看一下 IComparable<T> 界面。为了让事情更简单,让我们把它 IComparable<int>

IComparable<int>有这个方法:

int CompareTo(int other);

这基本上意味着

Everything that implements IComparable<int> has the ability to be compared with an integer. We can call its CompareTo method to decide whether it is bigger than, equal to, or less than any int.

由于所有实现 IComparable<int> 的东西都必须有方法 CompareTo,因此可以保证您调用 CompareTo 的对象可以与 int 进行比较.

那么这有什么用呢?

对数组进行排序时,与其他对象进行比较的功能很有用。 Array.Sort 方法使用 IComparable<T> 接口。以下是 Sort 方法的工作原理(高度简化):

它首先通过检查数组元素是否实现IComparable<T>来检查是否可以比较数组的元素。如果他们这样做,请通过调用 CompareTo 来比较它们!为什么可以这么肯定有一个叫CompareTo的方法呢?因为对象都实现了IComparable<T>,这保证了一个CompareTo方法!比较后,Sort可以判断哪个元素在前,哪个在后。

你知道不同的事物需要以不同的方式进行比较吗?整数和 double 可以通过一个减去另一个来比较,并检查结果是正数还是负数。但是字符串是按字母顺序比较的。如果没有 IComparable<T> 接口,则字符串会有不同的 Sort 方法,int 会有不同的 Sort 方法,还有 Sort可以比较的所有类型的方法。更糟糕的是,如果客户端代码创建了一些可以比较的东西,客户端代码需要编写自己的 Sort 方法!不要忘记还有大量其他方法可以利用比较功能。所有 那些 方法都需要为每种类型使用不同的版本吗?

这就是接口如此重要的原因。有了它们,只需要一个 Sort 方法,因为多态性会处理其余的事情。

让我总结一下接口的优点:

  • 它提供了灵活性(整数和字符串都可以与整数和字符串进行比较,但方式不同)
  • 它减少了代码(您不需要编写额外的 Sort 方法!)
  • 保证安全(如果你忘了实现某个方法,编译器会告诉你!)

我们假设IComparable<T>不存在,而stringint等可以比较的类型作为自己的CompareTo方法。 Sort 方法可以如下所示

public static void Sort(int[] arr) {
    // note that I'm using bubble sort here. A real sort method wouldn't use this coz it's slow.
    // source: 
    int temp = 0;

    for (int write = 0; write < arr.Length; write++) {
        for (int sort = 0; sort < arr.Length - 1; sort++) {
            if (arr[sort].CompareTo(arr[sort + 1]) > 0) {
                temp = arr[sort + 1];
                arr[sort + 1] = arr[sort];
                arr[sort] = temp;
            }
        }
    }

}

public static void Sort(string[] arr) {
    string temp = 0;

    for (int write = 0; write < arr.Length; write++) {
        for (int sort = 0; sort < arr.Length - 1; sort++) {
            if (arr[sort].CompareTo(arr[sort + 1]) > 0) {
                temp = arr[sort + 1];
                arr[sort + 1] = arr[sort];
                arr[sort] = temp;
            }
        }
    }

}

您可能会争辩说,一种采用 object[] 检查每个元素类型的 Sort 方法也可以工作。是的,但这与多个 Sort 方法存在相同的问题。您仍然需要添加另一个排序方法或另一个检查是否创建了新的可比较 class。

如果你有IComparable<T>界面,问题就解决了!

public static void Sort<T>(T[] arr) where T : IComparable<T> {
    T temp;

    for (int write = 0; write < arr.Length; write++) {
        for (int sort = 0; sort < arr.Length - 1; sort++) {
            if (arr[sort].CompareTo(arr[sort + 1]) > 0) {
                temp = arr[sort + 1];
                arr[sort + 1] = arr[sort];
                arr[sort] = temp;
            }
        }
    }
}

由于类型约束表明 T 必须实现 IComparable<T>,因此可以毫无问题地对数组中的每个对象调用 CompareTo

现在,如果您想添加另一个类似的 class,您需要做的就是实现该接口。而如果接口不存在,则必须编写一个 CompareTo 方法 一个 Sort 方法!