如何在新实现的接口或基类之间做出决定?

Posted

技术标签:

【中文标题】如何在新实现的接口或基类之间做出决定?【英文标题】:How to decide between an Interface or Base Class for an new implementation? 【发布时间】:2013-03-13 02:37:57 【问题描述】:

在实现方面,我应该如何决定选择基本类型还是接口?我试图研究几个例子,但我没有得到完整的想法:(

如何以及为什么将不胜感激的示例..

【问题讨论】:

如果一个抽象基类包含没有实现的方法,那么它也是一个接口。如果一个基类(没有 abstract 键)包含要在子类之间共享的具体实现,那么它就是它的本质——一个基类——;另一方面,接口需要它的子类来实现它的所有方法 好一个。谢谢@AspAsp 【参考方案1】:

基类,无论是否抽象,都可以包含已实现的成员。接口不能。如果您的所有实现都将执行类似的操作,那么基类可能是可行的方法,因为您的所有子类都可以共享基类上成员的相同实现。如果他们不打算共享实现,那么接口可能是要走的路。

例子:

class Person

    string Name  get; set; 


class Employee : Person

    string Company  get; set; 

Employee 从 Person 继承是有意义的,因为 Employee 类不必定义 Name 属性,因为它共享实现。

interface IPolygon

    double CalculateArea();


class Rectangle : IPolygon

    double Width  get; set; 
    double Height  get; set; 

    double CalculateArea()
    
        return this.Width * this.Height;
    


class Triangle : IPolygon

    double Base  get; set; 
    double Height  get; set; 

    double CalculateArea()
    
        return 0.5 * this.Base * this.Height;
    

因为RectangleTriangleCalculateArea 的实现如此不同,所以从基类继承它们没有意义。

如果你做了一个基类,发现里面只有包含抽象成员,你还不如只用一个接口。

而且,正如 j__m 所说,您不能从多个基类继承,但可以实现多个接口。

我通常先定义接口,如果我发现自己在我的实现中重复代码,我会创建一个实现接口的基类,并让我的实现继承自它。

【讨论】:

在您的第二个带有接口的示例中,它类似于拥有一个带有虚函数的基类,对吧?并且 Rectangle 可以继承自 Polygon 基类并实现其虚方法的变体 简单明了的解释。 +1 @TylerDurden,是的,它类似于拥有一个带有虚函数的基类。但是没有必要创建开销,因为没有派生类就没有使用其基类的意义,因此虚拟实现是无用的,只有覆盖功能在这种情况下才有用。【参考方案2】:

要决定是使用抽象类还是接口,我觉得这篇文章很有帮助Source:

对我来说,区分一种情况或另一种情况的好方法一直是:

    是否有许多类可以“组合在一起”并由一个名词描述?如果是这样,则以该名词的名称创建一个抽象类,并从中继承类。 (一个关键的决定因素是这些类共享功能,你永远不会只实例化一个Animal......你总是会实例化某种类型的Animal:你的实现动物基类) 例子CatDog都可以继承抽象类Animal,这个抽象基类会实现一个方法void Breathe(),所有动物都会以完全相同的方式执行此操作。 (我可能会将此方法设为虚拟,以便我可以为某些动物覆盖它,例如 Fish,它的呼吸方式与大多数动物不同)。

    哪些类型的动词可以应用于我的班级,通常也可以应用于其他人?为每个动词创建一个界面。 示例:所有动物都可以喂食,所以我将创建一个名为 IFeedable 的接口并让 Animal 实现它。只有 DogHorse 足以实现 ILikeable - 我不会在基类上实现它,因为这不适用于 猫。

请同时查看这个Interface vs Base class 问题。

【讨论】:

【参考方案3】:

使用抽象类的原因之一是我们必须强制执行一些初始化(例如通过构造函数的状态)。

接口不允许您定义构造函数的合同。

在下面的示例中,每个 Animal 对象都应该有一个 NAME。这不能通过接口强制执行。

public abstract class Animal

    public Animal(string name)
    
        this.Name = name;
    

    public string Name 
     
        get; 
        private set; 
    


public class Cat : Animal

    public Cat(string name)
        : base(name)
    

    

    string NoOfLegs  get; set; 




class Program

    static void Main(string[] args)
    
        Animal aCat = new Cat("a");
    

【讨论】:

正确的接口强制执行方法而不是数据字段。你的孩子必须包含这些功能,数据由他们决定。他们的合同和第 3 方供应商可能会利用合同来扩展您的部分代码。【参考方案4】:

实际上它们不一定是相互排斥的。您可以根据代码实现的发展方式同时使用这两种方法。 接口通常是合同的表述。它定义了实现者应该遵守的预期行为。并且通常认为针对 interfacespublic api 进行编码是一种很好的做法。这样可以减少与实现细节的耦合,并允许更轻松地重构和维护代码。现在,有资格称为公共 api 的是软件组件,它体现了您的设计中定义的高级交互,并且可以由您自己和其他人在同一项目中或在多个独立范围的项目中重用。 基类已经是实现的一部分。是否实现接口。并且它将其潜在的层次结构与特定的实现细节联系起来:对象状态、可覆盖或不可覆盖的方法等。

【讨论】:

【参考方案5】:

接口是一种更灵活的结构。你只能有一个基类,但你可以实现许多接口。如果您需要一个对象来支持多种行为,但其中不止一种行为需要特定的基类,那么您将无法这样做。

正如 Dan 所说,基类在许多语言中具有方便的优势,因为您可以提供基实现。要使用接口执行此操作,您需要创建一个提供基本实现的类,然后手动将每个接口方法的实现委托给该类 - 不太方便。

【讨论】:

【参考方案6】:

我在这里找到一个类比有帮助:

抽象基类:- 汽车制造商可能会开发一种具有一些变体(1.6、2L 等)的汽油发动机。发动机缸体铸造可以被认为是抽象基类,即它定义了发动机的基本形状和特征。 2L 版本可能有更大的气缸盖等。

接口:- 该引擎还可以使用各种现成的组件,例如交流发电机、散热器、起动马达等,因此它必须实现由这些组件定义的接口。这些组件通常是在不了解可能使用它们的引擎的情况下设计的。

【讨论】:

【参考方案7】:

如果需要,您基本上可以同时使用两者。但通常基类也可以用于继承类中使用的某些方法或属性。如果您创建一个基类,最好将其抽象化;通过在基类中使用虚方法并在继承类中覆盖它并以不同的方式使用,您将更加灵活。

有了接口,您就更灵活了。首先,您在从此接口继承的类之间进行声明。有了这个

让你的代码更具可读性

你把你的概念分开,就像从接口A继承的一个类可以连接Mssql数据库,而另一个可以连接mysql数据库。

你的代码变得更易于维护; Interfaces helps to reduce coupling and therefore allow you to easily interchange implementations for the same concept without the underlying code being affected

您可以使用dependency injection

您可以更轻松地创建单元测试。

您可以在此处查看this 问题以获取更多信息。

【讨论】:

以上是关于如何在新实现的接口或基类之间做出决定?的主要内容,如果未能解决你的问题,请参考以下文章

如何在选择不同类型的容器之间做出决定?

如何在 API 的 RPC 和 REST 之间做出决定?

Drupal Views 如何在具有相同路径的多个显示器之间做出决定

如何在使用消息传递(例如 RabbitMQ)与使用 Web 服务进行后端组件交互/通信之间做出决定?

Jenkins 如何在保留构建的天数和要保留的最大构建数量之间做出决定?

如何在开发 Web 应用程序和桌面应用程序之间做出决定 [关闭]