oop:接口与抽象类(通用OO)

最近我接受了两次电话采访,他们问我接口和抽象类之间的区别。我已经把我能想到的每一个方面都解释过了,但他们似乎在等我说一些具体的事情,而我不知道那是什么。

根据我的经验,我认为以下是正确的。如果我漏掉了一个要点,请告诉我。

接口:

接口中声明的每个方法都必须在子类中实现。接口中只能存在事件、委托、属性(c#)和方法。一个类可以实现多个接口。

抽象类:

子类只能实现抽象方法。抽象类可以有带实现的普通方法。抽象类还可以在事件、委托、属性和方法旁边有类变量。由于c#中不存在多继承,一个类只能实现一个抽象类。

在这之后,面试官提出了这样一个问题:"如果你有一个只有抽象方法的抽象类怎么办?"这和界面有什么不同?"我不知道答案,但我认为是上面提到的遗传,对吧?

另一个采访者问我,如果你在接口中有一个公共变量,那会和抽象类有什么不同?我坚持认为接口中不能有公共变量。我不知道他想听什么,但他也不满意。

参见:

何时使用接口而不是抽象类,反之亦然

接口与抽象类

如何在使用抽象类和接口之间做出选择?

接口和抽象类之间的区别是什么?


打个比方:当我在空军服役时,我参加了飞行员培训,成为了一名美国空军飞行员。那时我还没有资格飞任何东西,必须参加飞机类型的培训。一旦我合格了,我就是一个飞行员(抽象类)和一个C-141飞行员(具体类)。在我的一次任务中,我被赋予了额外的职责:安全官员。现在我仍然是一名飞行员和一名C-141飞行员,但我也担任安全官员的职责(可以说,我执行的是ISafetyOfficer)。飞行员不需要是安全官员,其他人也可以这样做。

所有美国空军飞行员都必须遵守空军的某些规定,所有C-141(或F-16、T-38)飞行员都是美国空军飞行员。任何人都可以成为安全官员。所以,总结一下:

飞行员:抽象类C-141飞行员:混凝土舱ISafety官员:接口

补充说明:这是一个类比,以帮助解释概念,而不是一个编码建议。请看下面的各种评论,讨论很有趣。


虽然您的问题表明它是针对"通用OO"的,但它似乎真正关注的是. net对这些术语的使用。

.NET中(类似于Java):

接口可以没有状态或实现实现接口的类必须提供该接口的所有方法的实现抽象类可能包含状态(数据成员)和/或实现(方法)抽象类可以在不实现抽象方法的情况下继承(尽管此类派生类本身就是抽象的)接口可能是多继承的,抽象类可能不是(这可能是接口与抽象类分开存在的主要具体原因——它们允许实现多继承,从而消除了一般MI的许多问题)。

作为一般的OO术语,这些区别并不一定定义得很好。例如,有些c++程序员可能拥有类似的严格定义(接口是抽象类的严格子集,不能包含实现),而有些人可能会说,具有某些默认实现的抽象类仍然是接口,或者非抽象类仍然可以定义接口。

实际上,有一个c++习语叫做非虚拟接口(NVI),其中公共方法是非虚拟方法,与私有虚拟方法"砰"的一声:

http://www.gotw.ca/publications/mill18.htmhttp://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-Virtual_Interface


我认为他们在寻找的答案是基本的或者OPPS哲学上的差异。

当派生类共享抽象类的核心属性和行为时,将使用抽象类继承。实际定义类的行为类型。

另一方面,当类共享不需要定义派生类的外围行为时,将使用接口继承。

如。一辆汽车和一辆卡车分享很多核心汽车抽象类的属性和行为,但他们也分享一些外围行为生成排气甚至非汽车类像钻孔机或发电机份额,不一定定义一辆汽车或卡车,汽车,卡车,钻孔机和PowerGenerator IExhaust都可以共享相同的接口。


简而言之:抽象类用于建模具有类似外观的类的类层次结构(例如,动物可以是抽象类,而人类、狮子、老虎可以是具体的派生类)

接口用于两个不关心类实现接口类型的相似/非相似类之间的通信。高度可以是界面属性,可以由人、建筑、树来实现。不管你会不会吃,会不会游泳,会不会死等等。它只与您需要有高度(在您的类中实现)有关。


还有一些其他的不同之处

接口不能有任何具体的实现。抽象基类可以。这允许您在那里提供具体的实现。这允许抽象基类实际提供更严格的契约,而接口实际上只描述如何使用类。(抽象基类可以有定义行为的非虚拟成员,这给基类作者提供了更多的控制。)

一个类可以实现多个接口。一个类只能从一个抽象基类派生。这允许使用接口实现多态层次结构,但不允许使用抽象基类。这还允许使用接口进行伪多继承。

抽象基类可以在v2+中修改而不破坏API。对接口的更改正在破坏更改。

(c# /。与抽象基类不同,NET特定的]接口可以应用于值类型(结构)。结构不能从抽象基类继承。这允许将行为契约/使用指南应用于值类型。


继承考虑一辆小汽车和一辆公共汽车。它们是两种不同的交通工具。不过,它们也有一些共同的特性,比如方向盘、刹车、齿轮、发动机等等。因此,使用继承概念,这可以表示为以下……

1
2
3
4
5
public class Vehicle {
    private Driver driver;
    private Seat[] seatArray; //In java and most of the Object Oriented Programming(OOP) languages, square brackets are used to denote arrays(Collections).
    //You can define as many properties as you want here ...
}

现在是自行车……

1
2
3
4
public class Bicycle extends Vehicle {
    //You define properties which are unique to bicycles here ...
    private Pedal pedal;
}

还有一辆车……

1
2
3
4
public class Car extends Vehicle {
    private Engine engine;
    private Door[] doors;
}

这就是遗传。我们使用它们将对象分类为更简单的基本形式及其子形式,如上所述。

抽象类

抽象类是不完整的对象。为了进一步理解它,让我们再次考虑一下车辆类比。车辆可以被驾驶。对吧?但是不同的车辆以不同的方式行驶……例如,你不能像骑自行车一样开车。那么如何表示车辆的驱动函数呢?要检查它是什么类型的车,并使用它自己的功能驾驶它就比较困难了;在添加新类型的车辆时,必须一次又一次地更改Driver类。下面是抽象类和方法的作用。您可以将drive方法定义为抽象方法,以说明每个继承的子元素都必须实现这个函数。如果你修改了vehicle类…

1
2
3
//......Code of Vehicle Class
abstract public void drive();
//.....Code continues

自行车和汽车也必须说明如何驾驶它。否则,代码将无法编译并抛出错误。总之. .抽象类是带有一些不完整函数的部分不完整类,继承子类必须指定它们自己的函数。

接口接口完全不完整。它们没有任何属性。它们只是表明继承的孩子有能力做某事……假设你有不同类型的手机。它们每一个都有不同的方式来完成不同的功能;打电话给某人。手机制造商详细说明了如何做到这一点。在这里,手机可以拨一个号码——也就是说,它是可拨的。让我们把它表示为一个接口。

1
2
3
public interface Dialable {
    public void dial(Number n);
}

在这里,拨号器的制造者定义了如何拨号。你只需要给它一个号码就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Makers define how exactly dialable work inside.

Dialable PHONE1 = new Dialable() {
    public void dial(Number n) {
        //Do the phone1's own way to dial a number
    }
}

Dialable PHONE2 = new Dialable() {
    public void dial(Number n) {
        //Do the phone2's own way to dial a number
    }
}


//Suppose there is a function written by someone else, which expects a Dialable
......
public static void main(String[] args) {
    Dialable myDialable = SomeLibrary.PHONE1;
    SomeOtherLibrary.doSomethingUsingADialable(myDialable);
}
.....

因此,使用接口而不是抽象类,使用可拨号的函数的作者不必担心它的属性。它有触摸屏还是拨号板?它是固定电话还是移动电话?你只需要知道它是否可以拨号;它是否继承(或实现)可拨号接口。

更重要的是,如果有一天你换了一个不同的拨号器

1
2
3
4
5
6
......
public static void main(String[] args) {
    Dialable myDialable = SomeLibrary.PHONE2; // <-- changed from PHONE1 to PHONE2
    SomeOtherLibrary.doSomethingUsingADialable(myDialable);
}
.....

您可以确保代码仍然能够完美地工作,因为使用可拨号接口的函数不(也不能)依赖于可拨号接口中指定的其他细节。它们都实现了一个可拨号接口,这是函数唯一关心的事情。

开发人员通常使用接口来确保对象之间的互操作性(可互换使用),只要它们共享一个公共功能(就像您可以切换到固定电话或移动电话,只要您只需要拨打一个号码)。简而言之,接口是抽象类的一个简单得多的版本,没有任何属性。另外,请注意,您可以实现(继承)任意多的接口,但是您只能扩展(继承)单个父类。

更多信息抽象类vs接口


如果您将java作为OOP语言来回答这个问题,那么Java 8发行版会导致上面答案中的一些内容过时。现在java接口可以使用默认方法来实现具体的实现。

Oracle网站提供了interfaceabstract类之间的关键区别。

考虑使用抽象类,如果:

您希望在几个密切相关的类之间共享代码。您希望扩展抽象类的类具有许多公共方法或字段,或者需要访问修饰符而不是public(如protected和private)。您希望声明非静态或非final字段。

考虑在以下情况下使用接口:

您期望不相关的类将实现您的接口。例如,许多不相关的对象可以实现Serializable接口。您希望指定特定数据类型的行为,但不关心谁实现了它的行为。您希望利用类型的多重继承。

简单来说,我想用

接口:通过多个不相关的对象实现契约

抽象类:在多个相关对象之间实现相同或不同的行为

看一下代码示例,以便以清晰的方式理解事物:我应该如何解释接口和抽象类之间的区别?


面试官们正对着一棵奇怪的树破口大骂。对于像c#和Java这样的语言,是有区别的,但是对于像c++这样的其他语言则没有区别。OO理论并没有区分这两者,仅仅是语言的语法。

抽象类是同时具有可继承的实现和接口(纯虚拟方法)的类。接口通常没有任何实现,只有纯虚拟函数。

在c#或Java中,没有任何实现的抽象类与接口的区别只在于用于继承接口的语法,以及您只能继承接口的事实。


通过实现接口,您正在实现组合("has-a"关系),而不是继承("is-a"关系)。这是一个重要的原则,当涉及到像设计模式这样的事情时,您需要使用接口来实现行为的组合,而不是继承。


我将解释接口和抽象类的深度细节。如果您了解接口和抽象类的概述,那么第一个问题就会出现在您的脑海中:什么时候应该使用接口,什么时候应该使用抽象类。所以请查看下面的接口和抽象类说明。

什么时候应该使用接口?

如果你不知道实现,我们只有需求说明,然后我们使用接口

什么时候应该使用抽象类?

如果您知道实现但不完全(部分实现),那么我们将使用抽象类。

接口

默认情况下,每个方法的公共抽象都意味着接口是100%纯抽象的。

摘要

可以有具体的方法和抽象的方法,具体的方法是什么,它们在抽象类中有实现,抽象类是声明为抽象的类——它可能包含也可能不包含抽象方法。

接口

我们不能将接口声明为受保护的私有接口

问:为什么我们没有声明接口是私有的和受保护的?

因为默认情况下接口方法是公共抽象的所以我们没有声明接口为私有和受保护的。

接口方法此外,我们不能声明接口为私有的、受保护的、最终的、静态的、同步的、本机的……

我将给出理由:为什么我们没有声明同步方法,因为我们不能创建接口的对象,同步是对象上的工作,所以和儿子的原因,我们没有声明同步方法瞬态概念也不适用,因为瞬态工作是同步的。

摘要

我们很乐意使用公共的,私有的final静态…在抽象上不适用任何限制。

接口

变量在接口中声明为默认的公共静态final,所以我们也没有声明为私有的、受保护的变量。

Volatile修饰符在接口中也不适用,因为接口变量在默认情况下是公共静态final, final变量一旦将值赋值给变量,就不能更改值,一旦将变量声明为接口,就必须赋值给变量。

而易失性变量是不断变化的,所以它是opp. to final,这就是我们在接口中不使用易失性变量的原因。

摘要

抽象变量不需要声明公共静态final。

我希望这篇文章是有用的。


从概念上讲,保持特定于语言的实现、规则、好处,以及通过使用任何人或两者都使用来实现任何编程目标,都可以或不可以拥有代码/数据/属性,等等,单个或多个继承

抽象(或纯抽象)类用于实现层次结构。如果您的业务对象在结构上看起来有些类似,那么只会使用继承/抽象类来表示父子(层次结构)关系。如果您的业务模型没有层次结构,那么就不应该使用继承(这里我不是在讨论编程逻辑,例如一些设计模式需要继承)。从概念上讲,抽象类方法在OOP实现业务模型的层次结构,它与接口,实际上比较抽象类和接口是没有意义的,因为在概念上都是完全不同的东西,这是在采访中问的概念来检查时因为它看起来都提供相同有些功能实现而言,我们的程序员通常更强调编码。[请记住抽象与抽象类是不同的]。

接口是一个契约,一个完整的业务功能由一个或多个功能集表示。这就是为什么它是实现的而不是继承的。业务对象(层次结构的一部分或不是)可以具有任意数量的完整业务功能。它与抽象类无关,通常表示继承。例如,一个人可以跑,一头大象可以跑,一只鸟可以跑,等等,所有这些不同层次的对象都将实现RUN接口或EAT或SPEAK接口。不要进行实现,因为您可能会为实现这些接口的每种类型都提供抽象类。任何层次结构的对象都可以具有与其层次结构无关的功能(接口)。

我相信,接口没有发明实现多个遗产或公开的公共行为,同样的,纯抽象类不否决接口但接口是一个功能,一个对象可以做(通过该接口的功能)和抽象类表示层次结构产生孩子的父母有父母的核心结构(属性+功能)

当您被问及差异时,实际上是概念上的差异,而不是特定于语言的实现上的差异,除非明确地询问。

我相信,两位面试官都希望这两句话有一个直接的区别,而当你失败时,他们试图把你引向这个区别,把其中一句说成另一句

What if you had an Abstract class with only abstract methods?


对于. net,

你对第二个面试官的回答也是对第一个面试官的回答。抽象类可以有实现,而状态,接口不能…

编辑:另一方面,我甚至不会使用短语"子类"(或"继承"短语)来描述"定义来实现"接口的类。对我来说,接口是一个契约的定义,如果一个类被定义为"实现"这个接口,那么它必须符合这个契约。它不会继承任何东西……你必须自己明确地添加所有东西。


我认为他们不喜欢你的回答,因为你给出的是技术上的差异,而不是设计上的差异。这个问题对我来说就像一个巨怪的问题。事实上,接口和抽象类具有完全不同的性质,所以您不能真正地对它们进行比较。我将向您展示我对接口的角色和抽象类的角色的看法。

接口:用于确保契约和类之间的低耦合,以便具有更可维护、可伸缩和可测试的应用程序。

抽象类:仅用于在具有相同响应能力的类之间分解某些代码。注意,这是OOP中多继承不好的主要原因,因为类不应该处理很多响应能力(而是使用组合)。

因此,接口具有真正的体系结构角色,而抽象类几乎只是实现的一个细节(当然,如果使用正确的话)。


接口:如果您想对组件暗示一个规则,那么应该使用接口,而这个规则可能存在,也可能不存在相互关联的

优点:

允许多重继承通过不公开在上下文中使用的对象的确切类型来提供抽象通过合同的特定签名提供一致性

缺点:

必须实现所有定义的契约吗不能有变量或委托一旦定义,就不能在不破坏所有类的情况下更改

抽象类:应该用于希望为彼此相关的组件提供一些基本或默认行为或实现的地方

优点:

速度比界面在实现上具有灵活性(您可以完全实现或部分实现)可以在不破坏派生类的情况下轻松更改

缺点:

不能被实例化不支持多重继承


1
2
3
After all that, the interviewer came up with the question"What if you had an
Abstract class with only abstract methods? How would that be different
from an interface?"

文档明确指出,如果抽象类只包含抽象方法声明,则应该将其声明为接口。

1
2
An another interviewer asked me what if you had a Public variable inside
the interface, how would that be different than in Abstract Class?

接口中的变量默认为公共静态变量和final变量。问题可以这样构造:如果抽象类中的所有变量都是公共的怎么办?与接口中的变量不同,它们仍然可以是非静态的和非final的。

最后,我想对上面提到的内容再补充一点——抽象类仍然是类,属于单个继承树,而接口可以出现在多个继承中。


接口:我们不实现(或定义)方法,而是在派生类中实现(或定义)方法。我们不在接口中声明成员变量。接口表示HAS-A关系。这意味着它们是物体的面具。抽象类:我们可以在抽象类中声明和定义方法。我们隐藏它的构造函数。这意味着没有直接从它创建对象。抽象类可以保存成员变量。派生类继承到抽象类意味着派生类中的对象没有被屏蔽,而是继承到抽象类。这种情况下的关系是- a。

这是我的观点。


由Jeffrey Richter通过c#从CLR复制…

我经常听到这样的问题:"我应该设计基类型还是接口?"答案并不总是一目了然。

以下是一些可能会对你有所帮助的指南:

一个类型只能继承一个实现。如果派生类型不能声明与基类型的IS-A关系,不要使用基类型;使用一个接口。接口意味着一种"我能做"的关系。如果CAN-DO功能似乎属于此功能对于各种对象类型,使用接口。例如,类型可以转换自身的实例对于另一种类型(IConvertible),类型可以序列化自身的一个实例(ISerializable),等。注意,值类型必须从System派生。ValueType,因此,它们不能派生自任意基类。在这种情况下,必须使用CAN-DO关系并定义一个接口。

作为一名开发人员,您通常更容易定义从a派生的新类型基类型,而不是实现接口的所有方法。基类型可以提供很多功能,因此派生类型可能只需要对其行为进行相对较小的修改。如果提供接口,则新类型必须实现所有成员。

一致性的实现,不管接口契约文档化得有多好不太可能每个人都100%正确地执行合同。事实上,COM存在这个问题,这就是为什么有些COM对象只能正确地工作微软Word或Windows Internet Explorer。通过提供一个良好的基类型默认实现,您开始使用的是一种有效且经过良好测试的类型;然后,您可以修改需要修改的零件。

如果向基类型添加方法,派生类型继承新方法,您开始使用一种有效的类型,甚至不需要重新编译用户的源代码。向接口添加新成员会强制接口的继承程序更改它的源代码和重新编译。


接口定义服务或服务集的契约。它们以水平方式提供多态性,因为两个完全不相关的类可以实现相同的接口,但是可以作为它们实现的接口类型的参数互换使用,因为这两个类都承诺满足接口定义的服务集。接口不提供实现细节。

抽象类为其子类和可选的部分实现定义基本结构。抽象类以垂直但定向的方式提供多态性,在这种情况下,继承抽象类的任何类都可以被视为抽象类的实例,而不是相反。抽象类可以而且经常包含实现细节,但不能单独实例化它们——只有它们的子类可以"更新"。

注意,c#也允许接口继承。


由于您可能已经从专家那里获得了理论知识,所以我不会在这里花太多的篇幅来重复这些知识,而是让我用一个简单的例子来说明我们可以/不可以使用InterfaceAbstract class

假设您正在设计一个应用程序来列出汽车的所有特性。在很多方面你都需要继承一些共同的东西,比如数字燃油表、空调、座椅调节等,这些都是所有汽车都具备的。同样,我们只需要对某些类进行继承,因为制动系统(ABS、EBD)等一些属性只适用于某些汽车。

下面的类作为所有汽车的基类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Cars
{
    public string DigitalFuelMeter()
    {
        return"I have DigitalFuelMeter";
    }

    public string AirCondition()
    {
        return"I have AC";
    }

    public string SeatAdjust()
    {
        return"I can Adjust seat";
    }
}

假设每辆车都有一个单独的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Alto : Cars
{
    // Have all the features of Car class    
}

public class Verna : Cars
{
    // Have all the features of Car class + Car need to inherit ABS as the Braking technology feature which is not in Cars        
}

public class Cruze : Cars
{
    // Have all the features of Car class + Car need to inherit EBD as the Braking technology feature which is not in Cars        
}

考虑到我们需要一种方法来继承汽车的刹车技术Verna和Cruze(不适用于Alto)。虽然两者都使用制动技术,但"技术"是不同的。因此,我们正在创建一个抽象类,在这个抽象类中,方法将被声明为抽象,并且它应该在其子类中实现。

1
2
3
4
public abstract class Brake
{
    public abstract string GetBrakeTechnology();
}

现在我们试图从这个抽象类继承,刹车系统的类型是在Verna和Cruze中实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Verna : Cars,Brake
{
    public override string GetBrakeTechnology()
    {
        return"I use ABS system for braking";
    }      
}

public class Cruze : Cars,Brake
{
    public override string GetBrakeTechnology()
    {
       return"I use EBD system for braking";
    }        
}

看到上面两个类中的问题了吗?它们继承自c#的多个类。Net不允许,即使方法是在子对象中实现的。这就需要接口。

1
2
3
4
interface IBrakeTechnology
{
    string GetBrakeTechnology();
}

具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Verna : Cars, IBrakeTechnology
{
    public string GetBrakeTechnology()
    {
        return"I use ABS system for braking";
    }
}

public class Cruze : Cars, IBrakeTechnology
{
   public string GetBrakeTechnology()
   {
       return"I use EBD system for braking";
   }        
}

现在Verna和Cruze可以在接口的帮助下,利用自己的制动技术实现多重继承。


大多数回答都集中在抽象类和接口之间的技术差异上,但由于从技术上讲,接口基本上是一种抽象类(没有任何数据或实现),我认为概念上的差异要有趣得多,这可能是面试官想要的。

接口是一种协议。它规定:"这是我们如何相互交谈"。它不能有任何实现,因为它不应该有任何实现。这是一个合同。它类似于C中的.h头文件。

抽象类是不完整的实现。类可以实现接口,也可以不实现接口,抽象类不必完全实现接口。没有任何实现的抽象类是无用的,但是完全合法。

基本上,任何类,无论抽象与否,都是关于它是什么,而接口则是关于如何使用它。例如:Animal可能是一个抽象类,它实现了一些基本的代谢功能,并指定了用于呼吸和运动的抽象方法,但没有给出实现,因为它不知道应该通过鳃还是肺呼吸,也不知道它是飞行、游泳、散步还是爬行。Mount,另一方面,可能是一个接口,它指定您可以骑在动物身上,而不知道它是哪种动物(或者它是否是动物!)

在幕后,接口基本上是一个抽象类,只有抽象方法,这并不重要。从概念上讲,他们扮演着完全不同的角色。


接口是执行特定行为的轻量级方法。这是一种考虑方法。


1)一个接口可以看作是一个纯抽象类,是一样的,但尽管如此,实现一个接口并继承自一个抽象类是不一样的。当您从这个纯抽象类继承时,您正在定义一个层次结构—>继承,如果您实现的接口不是您想要的接口,那么您可以实现任意多个接口,但是您只能从一个类继承。

2)可以在接口中定义属性,因此实现该接口的类必须具有该属性。

例如:

1
2
3
4
  public interface IVariable
  {
      string name {get; set;}
  }

实现该接口的类必须具有类似的属性。


虽然这个问题很老了,但我想补充一点,支持接口:

接口可以使用任何依赖项注入工具进行注入,而只有很少人支持抽象类注入。


回答第二个问题:interface中定义的public变量默认为static final,而abstract类中的public变量是一个实例变量。


接口类型与抽象基类

改编自Pro c# 5.0和。net 4.5框架书。

接口类型可能看起来非常类似于抽象基类。回忆当一个类被标记为抽象时,它可以定义任意数量的抽象成员来提供所有派生类型的多态接口。然而,即使类定义了一组抽象成员,也可以自由定义任意数量的构造函数、字段数据、非抽象成员(with)实现),等等。另一方面,接口只包含抽象的成员定义。由抽象父类建立的多态接口有一个主要的限制因为只有派生类型支持抽象父类定义的成员。然而,在大在软件系统中,开发多个没有公共父类的类层次结构是很常见的System.Object之外。假设抽象基类中的抽象成员只应用于派生类类型,我们无法在不同层次结构中配置类型来支持相同的多态接口。举个例子,假设你已经定义了以下抽象类:

1
2
3
4
5
6
7
8
public abstract class CloneableType
{
// Only derived types can support this
//"polymorphic interface." Classes in other
// hierarchies have no access to this abstract
// member.
   public abstract object Clone();
}

根据这个定义,只有扩展CloneableType的成员才能支持Clone()方法。如果您创建了一组不扩展这个基类的新类,则无法获得这个多态接口。另外,您可能还记得c#不支持类的多重继承。因此,如果你想创造一辆小型货车,它既是一辆轿车,又是一种CloneableType,你就无法做到:

1
2
3
4
5
// Nope! Multiple inheritance is not possible in C#
// for classes.
public class MiniVan : Car, CloneableType
{
}

正如您所猜测的,接口类型起到了拯救作用。定义了接口之后,就可以了可由任何类或结构、任何层次结构、任何名称空间或任何程序集中的任何类或结构实现(用任何。net编程语言编写)。正如您所看到的,接口是高度多态的。考虑在系统命名空间中定义的标准. net接口ICloneable。这接口定义了一个名为Clone()的方法:

1
2
3
4
public interface ICloneable
{
object Clone();
}

从我的另一个回答,主要是关于什么时候使用一个和另一个:

In my experience, interfaces are best
used when you have several classes
which each need to respond to the same
method or methods so that they can be
used interchangeably by other code
which will be written against those
classes' common interface. The best
use of an interface is when the
protocol is important but the
underlying logic may be different for
each class. If you would otherwise be
duplicating logic, consider abstract
classes or standard class inheritance
instead.


从编码的角度来看

如果抽象类只有抽象方法,则接口可以替换抽象类。否则,将抽象类更改为接口意味着您将失去继承提供的代码重用性。

从设计的角度来看

如果它是一个"Is a"关系,并且需要一个子集或所有功能,那么就将它作为一个抽象类。如果是"应该做"的关系,就把它作为接口。

决定您需要什么:仅仅是策略执行,或者代码重用性和策略。


理解OOP中的接口和抽象类的行为(以及语言如何处理它们)当然很重要,但我认为理解每个术语的确切含义也很重要。您能想象if命令不能完全按照术语的意思工作吗?而且,实际上一些语言正在减少,甚至更多,接口和抽象之间的差异…如果有一天这两个术语碰巧几乎是一样的,至少你可以自己定义它们应该用在哪里(以及为什么)。

如果你通读一些字典和其他字体,你可能会发现相同的词有不同的意思,但有一些共同的定义。我认为我在这个网站上找到的这两个意思是非常非常好的和合适的。

接口:

A thing or circumstance that enables separate and sometimes incompatible elements to coordinate effectively.

文摘:

Something that concentrates in itself the essential qualities of anything more extensive or more general, or of several things; essence.

例子:

你买了一辆车,它需要燃料。

enter image description here

您的汽车模型是XYZ,属于ABC类型,所以它是一辆具体的汽车,是一辆汽车的具体实例。汽车不是一个真正的物体。事实上,它是创建特定对象的一组抽象标准(质量)。简而言之,Car是一个抽象的类,它是"一种集中于自身的东西,具有任何更广泛或更普遍的本质品质"。

唯一符合汽车手册规格的燃料应该被用来填满汽车油箱。在现实中,没有什么可以限制你添加任何燃料,但是引擎只能在指定的燃料下正常工作,所以最好遵循它的要求。这些要求说,它和其他同类型的汽车一样,接受一套标准的燃料。

在面向对象的视图中,类型ABC的燃料不应该声明为类,因为没有针对特定类型的汽车的具体燃料。虽然您的汽车可以接受抽象类燃料或车用燃料,但您必须记住,只有一些现有的车用燃料符合规范,即实现您的汽车手册中的要求的那些。简而言之,他们应该实现接口ABCGenreFuel,其中"…使独立的、有时不兼容的元素能够有效地协调。

附录

另外,我认为你应该记住class这个词的意思,它(来自前面提到的同一个网站):

类:

A number of persons or things regarded as forming a group by reason of common attributes, characteristics, qualities, or traits; kind;

这样,类(或抽象类)不应该只表示公共属性(如接口),而应该表示具有公共属性的某种组。接口不需要表示一种类型。它必须表示公共属性。这样,我认为类和抽象类可以用来表示不应该经常改变其方面的东西,比如人类和哺乳动物,因为它表示一些种类。物种不应该经常改变自己。


Interface:- = =合同。无论哪个类实现它,都必须遵循接口的所有规范。

一个实时的例子是任何ISO标记的产品。ISO给出了一组rules/specification关于产品应该如何构建以及minimum set of features it Must拥有什么。

这只是subset of properties产品必须有的。ISO将签署产品only if it satisfies the its standards

现在看看这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface IClock{       //defines a minimum set of specification which a clock should have

    public abstract Date getTime();
    public abstract int getDate();
}
public class Fasttrack: Clock {
    // Must have getTime() and getTime() as it implements IClock
    // It also can have other set of feature like
    public void startBackgroundLight() {
        // watch with internal light in it.
    }
    .... //Fastrack can support other feature as well
    ....
    ....
}

这里,一个Fastrack被称为watch,因为it has all that features that a watch must suppost(最小功能集)。

原因及时间摘要:

从MSDN:

abstract class的目的是提供多个派生类可以共享的common definition of a base class

例如,一个类库可以定义一个抽象类,作为它的许多函数的参数,并要求使用这个库的程序员通过创建派生类来提供自己的类实现。Abstract simply means if you cannot define it completely declare it as an abstract . implementation类将完成此实现。

E。g -:假设我声明一个类Recipe为抽象类,但我不知道该使用哪个配方制造的。然后我将概括这个类来定义common definition of any recipe。菜谱的植入取决于菜品的实施。

抽象类可以由抽象方法组成,也可以由非抽象方法组成,因此您可以注意到接口的不同。所以不一定你的实现类必须有每个方法。您只需要覆盖抽象方法。

如果你想要tight coupling use Interface o/w use in case of lose coupling Abstract Class


其他几个不同点:

抽象类可以有静态方法、属性、字段等,而操作符、接口则不能。强制转换操作符允许强制转换到/从抽象类,但不允许强制转换到/从接口。

因此,即使抽象类从未实现(通过它的静态成员),而且不能以任何方式单独使用接口,您也可以单独使用抽象类。


除了比较Abstract classInterface之外,比较Abstract classConcrete class是有意义的。

使用抽象类就像使用具体类一样,只是在不需要/无法实例化它们而不进行扩展的情况下除外。并创建您不想要/无法实现抽象方法的方法。

如果您喜欢类比,可以将chromium看作一个抽象类(不能将它用作浏览器,因此不能实例化它),将chrome和opera看作是从chromium派生出来的具体类,将浏览器插件结构看作接口。


抽象类处理对类功能的有效打包,而接口用于意图/契约/通信,并且应该与其他类/模块共享。

同时使用抽象类作为契约和(部分)契约实现者违反了SRP。使用抽象类作为契约(依赖项)限制了创建多个抽象类以获得更好的重用性。

在下面的示例中,使用抽象类作为OrderManager的契约将会产生问题,因为我们有两种不同的处理订单的方法——基于客户类型和类别(客户可以是直接的或间接的,也可以是金或银)。因此,接口用于契约,抽象类用于不同的工作流实施

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public interface IOrderProcessor
{
    bool Process(string orderNumber);
}

public abstract class CustomerTypeOrderProcessor: IOrderProcessor
{
    public bool Process(string orderNumber) => IsValid(orderNumber) ? ProcessOrder(orderNumber) : false;

    protected abstract bool ProcessOrder(string orderNumber);

    protected abstract bool IsValid(string orderNumber);
}

public class DirectCustomerOrderProcessor : CustomerTypeOrderProcessor
{
    protected override bool IsValid(string orderNumber) => string.IsNullOrEmpty(orderNumber);

    protected override bool ProcessOrder(string orderNumber) => true;
}

public class InDirectCustomerOrderProcessor : CustomerTypeOrderProcessor
{
    protected override bool IsValid(string orderNumber) => orderNumber.StartsWith("EX");

    protected override bool ProcessOrder(string orderNumber) => true;
}

public abstract class CustomerCategoryOrderProcessor : IOrderProcessor
{
    public bool Process(string orderNumber) => ProcessOrder(GetDiscountPercentile(orderNumber), orderNumber);

    protected abstract int GetDiscountPercentile(string orderNumber);

    protected abstract bool ProcessOrder(int discount, string orderNumber);
}

public class GoldCustomer : CustomerCategoryOrderProcessor
{
    protected override int GetDiscountPercentile(string orderNumber) => 15;

    protected override bool ProcessOrder(int discount, string orderNumber) => true;

}

public class SilverCustomer : CustomerCategoryOrderProcessor
{
    protected override int GetDiscountPercentile(string orderNumber) => 10;

    protected override bool ProcessOrder(int discount, string orderNumber) => true;

}

public class OrderManager
{
    private readonly IOrderProcessor _orderProcessor;// Not CustomerTypeOrderProcessor or CustomerCategoryOrderProcessor

    //Using abstract class here would create problem as we have two different abstract classes
    public OrderManager(IOrderProcessor orderProcessor) => _orderProcessor = orderProcessor;
}

接口:

Does not contain Impleamentations
Interface can inherit from a number of interfaces(Multiple inheritance supported)
Members are automatically public
May contain properties, methods, events and indexers

抽象类:

May/mayn't contain Implementations; At least one member will not be implemented.
A Class may inherit from a single Base class; multiple inheritance not allowed.
Members have access modifiers
May contain fields, properties, constructors, destructors, methods, events and indexers

这些是抽象类和接口之间的关键区别。