【问题标题】:What is the need to introduce interfaces, when we already have its super-set abstract classes?当我们已经有了它的超集抽象类时,还需要引入接口吗?
【发布时间】:2014-05-09 02:41:34
【问题描述】:

抽象类包含两种类型的方法 - 抽象(未实现)和具体(已实现)方法。而接口只包含未实现的方法。这意味着接口是抽象类的子集。那么为什么接口是在 C# (.Net) 中引入的呢?在我看来,有两个原因:

  1. 支持多重继承
  2. 支持 C# 中值类型(结构)的继承。

是否还有其他原因或一些我遗漏的隐藏概念?

【问题讨论】:

  • C# 不支持多重继承。一个类可以实现多个接口并子类化一个类。这就是在语言中包含接口的充分理由。
  • 我已经在我的问题中使用“多重继承”提到了这一点。请提出我提到的两点以外的其他点。
  • 我再重复一遍:C# 支持多重继承。请尽量多尊重这里的用户。毕竟,我们正在努力帮助您。
  • 公平地说,“继承”和“实现”虽然我理解是不同的概念,但并不是每个人都能理解的非常微妙的区别。鉴于 C# 中的语法是相同的(与 Java 不同,Java 有两个单独的关键字),我可以理解为什么有人可能会从接口“继承”的角度来思考。鉴于此,OP 至少在他的原始帖子中暗示 C# 不支持多重继承除了通过接口。
  • 您的论点的变体,@ManishDubey:由于您可以从多个接口继承但只能从单个类继承,因此类必须是接口的子集。我也希望人们不要再抱怨那个实施/继承协议。这有点像斯托曼说你不应该说Linux,你应该说GNU slash Linux

标签: c# oop interface abstract


【解决方案1】:

您缺少的是考虑两个类之间的关系。

继承(与抽象类一起使用)是is-a 关系。因此,如果您正在为兽医诊所开发应用程序,您可能会创建一个 Animal 抽象类,然后从中创建 Cat、Dog、Bird 和 Fish,因为 Cat is-a Animal、Dog is-a Animal 等等。

接口实现定义了can-do 关系。也许您希望能够在您的应用程序中打印一些东西(Invoice、Animal、CustomerProfile)。您不应该为此使用继承(即抽象类),因为 Invoice is-a Print 没有任何意义,但是 Invoice can-do Print、CustomerProfile can-do Print 确实有意义。

【讨论】:

    【解决方案2】:

    您列出的原因都是有效的(接口并不是真正的多重继承,但类确实可以实现多个接口)。从纯功能的角度来看,这些是主要优势,而且非常重要。

    此外,接口通常被视为比抽象类“更干净”,因此具有几个设计优势。使用接口,所有成员都可以被覆盖,从而为想要提供自己的实现的消费者保证最大的灵活性。对于抽象类,密封、抽象和虚拟方法的混合会使消费者难以理解。您不仅要了解每个方法的 API,还需要了解默认实现如何交互。例如,假设您想实现一个自定义集合类,该类在添加每个元素时打印它。想象有一个基类有 2 个方法:

    virtual void Add(T item);
    virtual void AddRange(IEnumerable<T> items);
    

    我们首先将 Add() 重写为:

    override void Add(T item) { Console.WriteLine(item); base.Add(item); }
    

    但是,我们如何处理 AddRange()?如果我们知道 AddRange() 的默认实现在后台调用 Add(),那么我们根本不必重写它。另一方面,如果默认实现做了一些不同的事情(可能调用了一些方法 AddInternal(),AddInternal() 也被 Add() 调用),那么我们必须重写 AddRange() 以显式调用 Add()。

    因此,强迫自己使用接口而不是抽象类可以使 API 更简洁、更灵活。对于抽象类,每个密封方法都可能失去灵活性(并且通常可以被接口上的扩展方法替换),并且每个虚拟方法都可能使消费者的事情变得更加复杂。

    【讨论】:

      【解决方案3】:

      主要区别在于语义:接口声明行为契约(“它可以做什么?”),而类(包括抽象类)声明特定实现(“它是如何完成的?”)。

      有了这个,“纯抽象类”,作为没有实现的类,确实可以充当接口(如果我们想象CLR支持多重继承)。

      但“可以行动”并不意味着“应该”。 object[] 可以充当List&lt;int&gt;,或者我们可以使用delegate 而不是event,或者我们可以使用按类型切换而不是泛型等 - 但在 C# 中,在 CLR 中这将是错误的。不过还是可以的。

      所以回答你的问题 - 你不应该考虑形式上的差异,你应该考虑语义。而在 C# 中,接口用于声明“它可以做什么”。

      您甚至可能会看到完全空的接口——只是因为有人想“标记”任何实现这种空接口的东西以满足某人的需求——而这纯粹是语义上的,并且接口的完全“合法”使用。

      【讨论】:

        【解决方案4】:

        接口用于解耦应用程序中的组件;为了避免可能影响整个系统的关系,您的应用程序中必须存在一个分区,将其分成抽象和具体的组件。

        具体组件必须指向抽象组件,否则实现中的更改可能会影响整个架构。分区可能存在于应用程序的不同方面,但它们必须始终遵守这一原则,以确保应用程序不会变得脆弱。

        接口是一种承诺合约中不存在任何实现的方式,并且引用此合约的组件不会在此合约的实现中受到更改。抽象类不能做出这种保证,因为它们允许实现。即使您决定使用纯抽象类,其他一些开发人员也可能会添加一些微小的实现作为快捷方式,因为它有助于下线。你怎么知道?

        当然可以决定需要对纯抽象类应用一个新的关键字,以让编译器检查它确实是一个纯抽象......并且接口概念又回来了!

        这就是为什么.Net中需要存在接口概念以及抽象类本身还不够的原因


        编辑补充:有趣的是我在看the Interface Segregation Principle episode of the "Clean Code" series by Robert Martin,他在这一集中指出,java或C#等语言的接口是设计者不愿意/懒惰解决@问题的结果987654322@(他用死亡的致命三角来说明)。我可能一直试图证明接口的存在是因为它存在而不是考虑它存在的原因,但我仍然认为接口是一个有用的构造,可以保证行为定义中没有代码。

        【讨论】:

          猜你喜欢
          • 2017-02-20
          • 1970-01-01
          • 2011-04-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-08-22
          相关资源
          最近更新 更多