C#之抽象类(abstract)与接口(interface)
今天我们来讲讲抽象类(abstract)与接口(interface),说起这两个,相信我们大家在学习的时候,多多少少都会有些疑惑,正如“小朋友你是否有很多问号!”一样.
那么今天我就带大家来看看,这两者之间到底存在着怎样的关系?
首先我们先对抽象类(abstract)和接口(interface)进行初步了解.
抽象类(abstract)
- 在类声明中使用abstract修饰符的类称为抽象类。
- 含有一个或一个以上的抽象成员的类,必须定义为抽象类。
- 当方法声明包含abstract修饰符时,称该方法为抽象方法,虽然抽象方法同时隐含为虚拟方法,但是它不能有virtual修饰符。
- 声明中包含abstract修饰的属性称为抽象属性。
- 抽象方法或属性没有方法体或属性访问器。
- 抽象类只能是其他类的基类,抽象类不能实例化,必须在继承中实现。
- 抽象类中可以包含非抽象成员。
这里我们对抽象类只进行简单介绍,接下来重点讲解接口(interface),想了解更多关于抽象和虚拟的知识,请关注我的博客,在上篇文章中有详细讲解.
(Virtual)虚拟与(abstract)抽象
接口(interface)
什么是接口?
- 接口就是类似于类的一种结构,比抽象类更抽象(可以把接口理解为一个纯的抽象类)
- 接口天生就是做父类的也就是说接口要想具有意义就必须要被继承
- 接口是包含一组虚拟方法的抽象类型,其中每一种方法都有其名称、参数和返回值。
- 接口是指对协定进行定义的引用类型,其他类型实现接口,以保证它们支持某些操作。
- 接口指定必须由类提供的成员或实现它的其他接口。
- 接口除了声明时的限制,接口的内容也只能包含方法、属性、事件和索引器。
- 接口可以理解为对一组方法进行统一命名,接口中的方法没有具体的实现,接口规定了所有子类的需要遵循的标准,然后继承该接口的子类都必须实现这些方法.
- 通俗的说接口定义了需要做些什么,但是没有具体的做法,做法的实现由派生类来做。
怎么使用接口?
- 接口的定义使用关键字Interface,由于需要子类继承,所以接口默认是public类型,自然private或者protected是不可用的。
- 接口的声明需要使用“I”开头进行标识。接口名称前面的大写字母“I”是一个约定,正如字段名以下划线开头一样,请坚持这些原则。
//Interface 接口名称{ 接口成员; }
public interface INewInterface
{
}
- 接口中所有内容,都不可以使用访问修饰进行修饰,public除外。
- 方法没有实现主体,只包含一个方法签名;
- 属性则只有一个属性访问器,但可以通过隐藏get或set方法,来实现属性的只读或者只写。
Interface INewInterface
{
int Age{get; set;}
void ShowAge();
}
- 继承接口的可以是类也可以是结构体,但是都需要实现接口中声明的成员
class NewClass:INewInterface{
public int Age{get; set;}
public void ShowAge(){
Console.WriteLine($"age:{Age}");
}
}
- 对于类而言,除了可以继承接口之外,最基本就是类与类之间的继承,
- 但是在C#中,一个类的派生类只能有一个,一个类的基类也只能有一个
- 但是继承的接口个数是没有限制的。
- 只是在书写上基类必须放在所有接口之前,基类与接口、接口与接口之间使用“,”进行分隔
class BaseClass
{
...
}
Interface IFirst
{
...
}
Interface ISecond
{
...
}
class ChildClass:BaseClass, IFirst, ISecond
{
}
- 接口之间也可以相互继承,进行扩展,但对于具体的子类来说,就需要实现所有继承的接口的成员
Interface IInterfaceA
{
void MethodA();
}
Interface IInterfaceB:IInterfaceA
{
void MethodB();
}
Class ClassA:IIterfaceB
{
public void MethodA()
{
...
}
public void MethodB()
{
...
}
}
在接口中我们需要注意什么?
-
接口不能被实例化,如果要实例化只能实例化它的实现类(子类)
-
在接口的方法全部为抽象方法,不能有实例方法,也不能有构造方法
-
一个类可以直接继承多个接口,但只能直接继承一个类(包括抽象类),当一个类继承某个接口时,它不仅要实现该接口定义的所有方法,还要实现该接口从其他接口中继承的所有方法。
-
一个类能同时实现多个接口,还能在实现接口的同时 在继承其他类,并且接口之间也可以继承.
-
接口方法不能包含任何实现,但是接口可以包含事件、属性、索引器、静态方法、静态字段、静态构造函数以及常数。但是注意:C#中不能包含任何静态成员。
-
接口成员不允许使用 public, private, protected, internal访问修饰符
-
接口中的成员不允许使用 static, virtual, abstract, sealed修饰符
-
接口中不能定义字段.
-
在接口中定义的方法不能包含方法体
-
在接口里面可以定义成员变量(属性),但是此成员变量默认为public final(可以省略)修饰,必须要赋初始值。不能有其它的权限修饰符修饰,只能是public,否则就报错
-
实现类可以实现N个接口,但是必须要实现N个接口里面所有的方法;如果只想重写一部分父类的方法,那子类就必须是抽象类
接口的意义?
在C#中,类之间的继承关系支持单继承, 而接口是为了实现多继承关系而设计的
接口(interface)与抽象类(abstract)之间的区别?
- 类是对 对象的抽象,可以把抽象类理解为把类当作对象,抽象成的类叫做抽象类.
- 而接口只是一个行为的规范或规定,微软的自定义接口总是后带able字段,证明其是表述一类类“我能做。。。”.
- 抽象类更多的是定义在一系列紧密相关的类间,而接口大多数是关系疏松但都实现某一功能的类中.
不同点 | 接口(interface) | 抽象类(abstract) |
---|---|---|
用interface定义 | 用abstract定义 | |
可以实现多个接口也就是多继承 | 只能继承一个类 | |
实现接口的类必须实现所有成员 | 非抽象子类必须实现抽象方法 | |
直接实现 | 需要重写(override)实现抽象方法 | |
接口能定义抽象规则 | 抽象类既可以定义规则,还可能提供已实现的成员。 | |
接口是一组行为规范 | 抽象类是一个不完全的类 | |
接口可以用于支持回调 | 抽象类不能实现回调,因为继承不支持 | |
接口只包含方法、属性、索引器、事件的签名,但不能定义字段和包含实现的方法 | 抽象类可以定义字段、属性、包含有实现的方法 | |
接口可以作用于值类型和引用类型 | 抽象类只能作用于引用类型 | |
在接口中,所有的方法都默认为public。 | 抽象类则不是 | |
接口则是抽象方法的集合 | 抽象类是用来捕捉子类的通用特性的 | |
子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 | |
接口多定义对象的行为 | 抽象类多定义对象的属性 | |
接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。 | 抽象类比接口速度要快 | |
如果你往接口中添加方法,那么你必须改变实现该接口的类。 | 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 |
相似点 | 接口(interface)与抽象类(abstract) |
---|---|
两者都不能被直接实例化,都可以通过继承实现其抽象方法 | |
都包含未实现的方法声明 | |
子类必须实现未实现的方法,抽象类是抽象方法,接口则是所有成员(不仅是方法包括其他成员) | |
都是面向抽象编程的技术基础,实现了诸多的设计模式。 |
以下是我在网上看到的几个形象比喻,真的非常不错,相信你看了这两个例子,你会对抽象类和接口有更深的理解:
- 飞机会飞,鸟会飞,他们都继承了同一个接口“飞”;但是F22属于飞机抽象类,鸽子属于鸟抽象类。
- 就像铁门木门都是门(抽象类),你想要个门我给不了(不能实例化),但我可以给你个具体的铁门或木门(多态);而且只能是门,你不能说它是窗(单继承);一个门可以有锁(接口)也可以有门铃(多实现)。门(抽象类)定义了你是什么,接口(锁)规定了你能做什么(一个接口最好只能做一件事,你不能要求锁也能发出声音吧(接口污染)。
什么时候使用接口(interface)与抽象类(abstract)?
要解决上面的问题,我们先从弄清楚抽象类和接口之间的关系。首先,我们都知道类对事物的抽象,定义了事物的属性和行为。而抽象类是不完全的类,具有抽象方法。接口则比类的抽象层次更高。所以,我们可以这样理解它们之间的关系:类是对事物的抽象,抽象类是对类的抽象,接口是对抽象类的抽象。
-
在设计类的时候,首先考虑用接口抽象出类的特性,当你发现某些方法可以复用的时候,可以使用抽象类来复用代码。
简单说,接口用于抽象事物的特性,抽象类用于代码复用。
-
如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。
-
如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。
-
抽象类和接口各有不可替代的作用。
下面我们提出三个问题:
抽象类必须要有抽象方法吗?
抽象类中不一定包含抽象方法,但是包含抽象方法的类一定要被声明为抽象类。
抽象类能使用 final 修饰吗?
- 抽象类不能用 final 来修饰。当用 final 修饰一个类时,表明这个类不能被继承。
- final 类中的所有成员方法都会被隐式地指定为 final 方法,这明显违背了抽象类存在的意义了。
一个类如何继承多个接口? 以及同时继承其他类?
- 接口之间的继承和类是相同的,继承的规则也是相同的
- 子类继承父类接口的内容,如果有多个接口的话,书写上父类必须放在所有接口之前,父类与接口、接口与接口之间使用“,”进行分隔
总结
- 总的来说,接口和抽象类是为了更好的实现类型之间继承关系而提供的语言手段,而且两者有些相辅相成的关系。
- 因此我并不强调用什么而不用什么,那么问题的关键在于,如何把这两种手段合理的应用到程序当中,这才是至关重要。
- 而且我们可以看的出来,抽象类和普通的类区别也不大,但还有一点接口的特性,像是普通类与接口之间的一个过渡。
- 不像接口那么抽象,但又比普通类更加高级一点,是一种比较灵活的存在。
隐式接口和显示接口
- 在实现接口的成员时有两种方式,一种是隐式实现接口成员,一种是显式实现接口成员。
- 在实际应用中隐式实现接口的方式比较常用,由于在接口中定义的成员默认是 public 类型的,隐式实现接口成员是将接口的所有成员以 public 访问修饰符修饰。
- 显式实现接口是指在实现接口时所实现的成员名称前含有接口名称作为前缀。
- 需要注意的是使用显式实现接口的成员不能再使用修饰符修饰,即 public、abstract、virtual、 override 等。
接下来将通过实例来演示在编程中隐式实现接口与显式实现接口有什么区别。
隐式实现接口成员
public interface IReview
{
void GetReviews();
}
public class ShopReview :IReview
{
//隐式实现
public void GetReviews(){}
}
都可以调用GetReviews这个方法。
IReview rv = new ShopReview(); rv.GetReviews();
ShopReview rv = new ShopReview(); rv.GetReviews();
显式实现接口成员
public interface IReview
{
void GetReviews();
}
public class ShopReview :IReview
{
//显式实现
void IReview.GetReviews(){}
}
通过这种方式的接口实现。GetReviews就只能通过接口来调用:
IReview rv = new ShopReview();rv.GetReviews();
下面的这种方式将会编译错误
ShopReview rv = new ShopReview();rv.GetReviews();
总结:
-
显示接口中的方法没有修饰符,隐式接口方法的修饰符为public
-
显示接口中的方法可以看到从哪里来【通过接口访问,避免访问歧义】,来源相当清晰,隐式接口看不出来源
-
显示接口会把父级接口中的方法和属性完全继承,隐式接口会过滤冗余的方法
-
最后用类的方式调用方法会出错,需要用as转换接口类型,平常项目的使用一般都是实现隐式接口,具体要看实际情况设计。
结论:
- 隐示实现接口和类都可以访问
- 显示实现只有接口可以访问。
- 也就是,接口可以访问显示实现和隐式显示,而类只可以访问隐式实现。
显示实现的好处:
- 隐藏代码的实现
- 当用类的实例的时候,没有办法知道它有实现的接口的方法。
- 在使用接口访问的系统中,限制调用者只能通过接口调用而不是底层的类来访问。