从代码的语法定义和使用逻辑两个方面浅谈接口与抽象类的区别.网络
1 语法定义篇dom
(1)首先是定义语法ide
接口this
接口的定义是spa
[访问修饰符] interface 接口名设计
{3d
// 接口成员code
}对象
抽象类blog
抽象类的定义是
[访问修饰符] abstract class 类名
{
// 类成员
}
定义语法中接口关键字interface,类关键字class没什么差别,抽象类多一个abstract修饰
(2) 成员类型
接口
对于接口,它是定义的一类能力,所以以功能为主,面向一类抽象能力,因此成员只与方法有关. 那么记忆接口能定义什么,就记住方法即可.
举个例子,对于接口成员能够包含:方法、属性、事件和索引器.
实际上能够知道,属性是特殊的方法,事件是一个私有的委托变量加上两个方法,而索引实际上就是属性,所以对于接口成员的记忆,能够归结为“方法”
抽象类
抽象类的成员与通常类成员没什么区别,只不过抽象成员必须在抽象类中. 而在抽象类中通常定义的抽象成员有方法、属性、事件和索引器.
另外,抽象类能够有构造方法,其做用是为非抽象成员进行初始化,而不是做为建立抽象类的实例而使用
注:即便抽象构造方法不能被外界调用,可是也不能设定为private,由于其派生类会默认调用无参构造方法
(3)成员访问修饰
接口
接口就是为了被实现的,即每一个成员都是要被别的类进行实现的,那么每一个成员都应该是public类型,所以不用设定接口中成员类型的访问修饰符,默认为public,通常语法为
返回类型 方法名(参数列表);
抽象类
抽象成员必须设定为public,其他没有要求
(4)各抽象成员的定义
接口
方法的定义:
返回类型 方法名(参数列表);
属性的定义:
返回类型 属性名{ get; set; }
事件的定义:
event 委托名 事件名;
索引的定义:
返回类型 this[索引类型] { get; set; }
例如:
public interface IMyInterface { void Func(); string Value { get; set; } event EventHandler MyEventHandler; string this[int index] { get; set; } }
注:索引器与属性的get与set不一样于自动属性,是能够选择只读、只写、仍是读写的 通常接口都已I命名开始
抽象类
其抽象成员定义相似,可是通常在抽象类中的抽象成员以方法和属性居多.
首先抽象成员能够来自接口得到,只须要在每个成员的前面加上public abstract便可
例如:
public abstract class MyAbs : IMyInterface { public abstract void Func(); public abstract string Value { get; set; } public abstract event EventHandler MyEventHandler; public abstract string this[int index] { get; set; } }
其次,抽象类能够来自与某个基类,将基类的虚方法用abstract进行修饰,例如
public class BaseClass { public virtual void Func() { // 方法体 } } public abstract class MyAbs : BaseClass { public abstract void Func(); }
注:
一、方法没有方法体是指参数括号写完后没有花括号,直接分号结束;若是写成
public abstract void Func() { }
不叫做无方法体,只能称做空实现,这样会出现语法错误.
二、抽象成员必须是public的,同时由abstract修饰.
三、接口中的事件定义与抽象类中的事件定义意义和方法定义相同:
-> 在接口中定义一个事件,至关于同时定义了add和remove没有方法体的方法
void add_MyEventHandler(EventHandler value);
void remove_EventHandler(EventHandler value);
-> 在抽象类中,至关于两个abstract方法
public abstact void add_MyEventHandler(EventHandler value);
public abstact void remove_EventHandler(EventHandler value);
三、在其子类中能够被实现,对于接口实现add和remove方法,对于抽象类重写它们
(5)抽象成员的实现
实现接口
实现接口的类分三种,一是抽象类,二是通常的类,再就是结构. 而实现方式又分为实现接口和显式实现接口.
抽象类
抽象类实现接口,只须要将接口中的成员与方法添加好就好了,可是必须保证成员的访问级别为public
例如:
public abstract class MyAbs : IMyInterface { public void Func() { // 实现代码 } public string Value { get; set; } public event EventHandler MyEventHandler; public string this[int index] { get { // 实现代码 } set { // 实现代码 } } }
注意:
一、此处必定不容许丢掉public,由于接口就是用来定义方法,最后是要被访问的,若是不设为public,那么会出现问题.
二、此处的属性与接口中的属性意义是不一样的,此处为自动属性,在代码的后台编译器会自动生成一个字段,并填补get和set读取器.
三、此处的事件也与接口中的事件意义不一样(从代码角度看几乎同样),这里的事件,编译器会自动生成一个同名的私有委托,并将add方法与remove方法补全.
若是不但愿实现接口中的方法,能够将接口成员直接copy下来,在前面加上abstract关键字便可. 例如
public abstract class MyAbs : IMyInterface { public abstract void Func(); public abstract string Value { get; set; } public abstract event EventHandler MyEventHandler; public abstract string this[int index] { get; set; } }
那么此时的各个成员,其本质与接口中的成员同样,只有方法签名,没有实现体.
对于“显式实现接口”,其实现方式就是将成员写成
接口名.成员名
的形式. 显式实现接口与通常的实现区别在于显式实现的成员只有接口对象才能调用.
实现方式代码以下
public abstract class MyAbs : IMyInterface { void IMyInterface.Func() { // 方法体 } string IMyInterface.Value { get; set; } event EventHandler IMyInterface.MyEventHandler { add { // 实现代码 } remove { // 实现代码 } } string IMyInterface.this[int index] { get { // 实现代码 } set
{ // 实现代码 } } }
注意:
一、这里显示实现的接口,不容许有访问修饰符. 由于显式实现的接口只能由接口对象来调用,这里不管写什么都会出现问题.
二、这里事件不容许使用默认系统自定义的方式,须要本身添加add方法和remove方法
三、另外显示实现接口的成员不容许使用abstract进行修饰.
普通类实现接口
对于普通的类实现接口,只须要将接口的成员的代码实现补全便可,这里与抽象类是同样的,一样对于事件,能够手动添加add方法和remove方法.
若是在实现的方法中,有部分须要在子类中被重写,那么只需在方法前加上virtual进行修饰便可,示例代码以下:
public class MyClass : IMyInterface { public virtual void Func() { // 方法体 } }
注:
一、这里的成员访问修饰符也必须是public.
二、这里一样能够“显式实现接口”,规则与方法与抽象类中介绍的一致
-> 不容许使用virtual修饰成员
-> 事件必须使用“事件访问器”语法
-> 成员没有访问修饰符
实现接口的除了类之外,接口也能够实现接口
实现抽象类
抽象类的实现与接口实现由点相似,不过这里不存在显式与非显式罢了. 在实现抽象类的过程当中,抽象类的每个抽象成员都须要提供实现代码,由于抽象类中包含没抽象成员,所以继承自抽象类的类,只用提供重写代码便可.
不过在语法中与接口的实现仍是有点不同. 接口中成员的实现与通常的写法是同样的,可是实现抽象类的成员,每个成员前面都要有override的修饰. 代码以下:
public class MyClass : MyAbs { public override void Func() { // 实现代码 } public override string Value { get; set; } public override event EventHandler MyEventHandler; public override string this[int index] { set { // 实现代码 } get { // 实现代码 } } }
注:
一、这里实现的成员必须使用public override进行修饰.
二、事件能够不本身提供add方法与remove方法
-> 此时编译器会自动添加一个私有的委托变量,以及add方法与remove方法
三、这里必须保证每个成员都要从新实现,这里示例代码中的属性是自定属性,所以看起来与抽象类中的同样,可是编译器会本身添加私有字段与get方法和get方法.
四、若是依旧不须要某些方法提供方法体,即仍旧是抽象成员,那么须要补全其余方法,将不须要方法提的方法用abstract修饰便可,同时不要忘记类也应该使用abstract进行修饰,由于抽象成员只能容许在抽象类中存在.
属性的实现
属性定义为抽象类型能够由三种形式:只读属性、只写属性和读写属性.
在实现属性的时候,前面提到的例子都是读写属性,下面来看看另外两种.
只读属性
只读属性的定义为:
public interface IMyInterface { string Value { get; } } public abstract class MyAbs { public abstract string Value { get; } }
注:经过前面的介绍,能够知道实现抽象成员,接口与抽象类是相似的,那么此处介绍以接口的实现为规范描述.
实现该属性的有两种方案,第一种就是提供一个字段,并添加读取器的实现代码,例如
public class MyClass : IMyInterface { private string _value; public string Value { get { return this._value; } } }
第二种方案即是补全两种读取器,例如
public class MyClass : IMyInterface { private string _value; public string Value { get { return this._value; } set { this._value = value; } } }
注:
一、对于第二种方案,在显式实现接口的时候不能使用,由于在接口中不存在补全的读取器.
二、对于添加进来的读取器可使用访问修饰符进行修饰,例如protected等.
(6)使用逻辑篇
接口与抽象类都是抽象的数据类型,虽然使用中有不少比较模糊的地方发,也就是说部分状况接口与抽象类的使用时能够互换的. 可是详细来讲接口与抽象类仍是有不少不一样的地方. 总的来讲就是注重功能的时候使用接口,注重对象的时候使用抽象类.
对于接口,从定义就能够发现是对方法的抽象,也可描述成对能力的抽象;而抽象类是对以事物的抽象,与具体的对象有关. 这么说很混乱,直接上例子看看.
先考虑如下接口代码:
public interface IMobilePhone { void Call(); void Message(); } public interface IProgram { void RunApp(); }
先对这两个接口作一个说明. 为了描述抽象类与接口在使用中的关系,我打算描述如下手机在生活中的状况,这样区分接口与抽象类的关系表现会明显许多.
这里定义一个接口IMobilePhone,用来表示手机最基本的能力“打电话”和“处理短信”,这里不考虑较复杂的状况,好比接电话与拨电话,发短信与收短信等.
定义接口IProgram用来描述智能机可以运行应用程序. 一样不考虑较复杂状况.
咱们知道,手机是一个很泛的概念,好比你试想一下——你见过手机吗?或许说你只能分辨什么是手机,若是让你来描述你明显的会用一个你们熟悉的另外一部手机来比拟,好比“和苹果同样能够怎么怎么”,“和诺基亚5800同样能够怎么样怎么样”等. 所以说手机其实是一个抽象的概念,能够将手机定义为一个抽象类,以下代码.
public abstract class MobilePhone : IMobilePhone { public virtual void Call() { Console.WriteLine(“打电话了”); } public virtual void Message() { Console.WriteLine(“处理电信了”); } }
注:
一、此处使用虚方法的缘由是考虑大多数手机的电话处理与短信处理类似,只有一些比较高级的手机处理短信会有彩信、蓝牙信息、多媒体信息等,所以等到其余具体智能机时再考虑重写.
二、这里考虑手机的基本功能,为描述接口与抽象类的一些区别,所以省略其余无关信息.
那么此处手机是有了,可是什么型号?什么品牌?很明显手机只是一个概念,具体到手机还有品牌,型号等不少信息,那么能够用这个抽象类派生一个诺基亚手机出来. 不过像诺基亚这样的大厂商有五、6k的手机,也有充100元话费赠送的手机,很显然,诺基亚手机也是抽象类. 代码以下:
public abstract class Nokia:MobilePhone { public readonly string Mark = “Nokia”; public abstract string FaceDesigner { get; } public abstract string Keyboard { get; } }
注:
一、这里定义的两个只读属性,FaceDesigner表示板式,是翻盖仍是直板;Keyboard表示键盘是虚拟仍是非虚拟.
二、定义一个只读的字段,表示手机品牌,而后具体的手机由该类来派生.
下面就100元手机和Nokia Lumia 900做个模拟(赶个潮流!哈哈!!!)
100元机型
直接上代码:
public class Nokia1280 : Nokia { static int index; string numMark; string faceDesigner; string keyboard; public Nokia1280() { numMark = base.Mark + “ 1280 No” + (++index).ToString(“0000”); faceDesigner = “直板机”; keyboard = “物理键盘”; } public string NumMark { get { return this.numMark; } } public override string FaceDesigner { get { return this.faceDesigner; } } public override string Keyboard { get { return this.keyboard; } } }
注:
一、由于每台手机都有一个惟一的序列号,这里使用自增静态的index来描述该机型的产量,每生产一部手机,就会自动增长一次. 并将数据与品牌进行字符串处理,赋值给numMark,便可知道这款手机的型号与编号了.
二、添加构造方法,设定为直板机型与物理键盘.
三、实现每个属性.
Nokia Lumia 900
因为这里是绝对的智能机,毫无疑问须要考虑能够执行应用程序的状况,所以该类继承自Nokia同时实现IProgram接口,可是智能机还有5800、N8等别的型号啊,因此很明显,智能机须要有一个类来进行描述,那即是一个智能机的抽象类. 代码以下
public abstract class NokiaApp : Nokia, IProgram { public abstract void RunApp(); }
而后再派生出Lumia900
public class Nokia_Lumia_900 : NokiaApp { static int index; string numMark; string faceDesigner; string keyboard; public Nokia_Lumia_900() { numMark = base.Mark + “ Lumia 900 No” + (++index).ToString(“0000”); faceDesigner = “直板机”; keyboard = “虚拟键盘”; } public override void Call() { Console.WriteLine(“用蓝牙打电话了”); } public override void Message() { Console.WriteLine(“处理短信、彩信、多媒体信息”); } public override void RunApp() { Console.WriteLine(“运行Windows Phone程序了”); } public string NumMark { get { return this.numMark; } } public override string FaceDesigner { get { return this.faceDesigner; } } public override string Keyboard { get { return this.keyboard; } } }
注:
一、智能手机实现IProgram接口,所以抽象类NokiaApp继承自Nokia,实现IProgram.
二、厂商可能会生产许多其余机型,所以在建立手机的时候须要考虑一个智能机的抽象类.
三、重写Call方法是因为智能机可使用更多的扩展实现打电话的功能,好比蓝牙、可视等.
四、重写Message一样由于智能机能够处理多媒体,而且能够轻松地与网络进行交互.
那么中规中矩,以上是一个比较完整的实现接口与抽象类的实例,但在实际开发中不见得会用到这么详细与复杂,可能根本不须要接口,或者根本不须要中间的这么多抽象类,这里把这部分描述得如此详细主要是为了方便比较两种方法在实际使用中的差别.
开始比较
面向手机的基本应用
若是简单的描述电话等能力,彷佛使用抽象类与接口没有多大区别,前面也讲过. 可是若是我手上的项目是一个考虑手机处理的一个项目,须要考虑手机的基本信息,包括电话功能、信息功能和一些简单的附属功能、好比闹钟等.
很明显,前文中的代码中使用Nokia这里抽象类足够描述全部信息了. 加上多态的实现,就能够很好的描述全部手机的基本信息.
在Main方法中添加以下代码,运行能够查看结果.
1 Random r = new Random(); 2 3 Nokia[] nks = new Nokia[100]; 4 5 for(int i = 0; i < nks.Length; i++) 6 7 { 8 9 if(r.Next(2) == 1) 10 11 { 12 13 nks[i] = new Nokia1280(); 14 15 } 16 17 else 18 19 { 20 21 nks[i] = new Nokia_Lumia_900(); 22 23 } 24 25 } 26 27 foreach(Nokia n in nks) 28 29 { 30 31 n.Call(); 32 33 n.Message(); 34 35 Console.WriteLine(“手机品牌是{0},外形设计是{1},键盘是{2}”, 36 37 n.Mark, 38 39 n.FaceDesigner, 40 41 n.Keyboard 42 43 ); 44 45 Console.WriteLine(); 46 47 }
注:这里只关注手机的所有基本功能,是面向一个手机对象的,换句话说须要包括全部的手机,但针对最基本和最全面的手机功能来进行实现,不用考虑较为复杂的手机扩展,很显然此处不太适合使用接口.
面向智能手机的应用
很明显,与上面的例子相似,咱们能够从NokiaApp这个抽象类中派生出其余型号的智能手机,例如N九、5800、C七、X七、E7等.
要处理智能手机的信息,就可使用NokiaApp这个抽象类与多态的实现,来处理全部的智能手机的打电话、信息处理、软件处理等全部功能. 很显然代码示例与上一个示例较相似,这里就很少列举了.
最后要提到的就是,这里面向的仍然是手机这个对象,所以也不太方便使用接口,应该使用抽象类.
面向电话与信息平台
上面都使用了抽象类型,可是面对的成员是手机这个看似具体的对象. 可是代码中仍然有比较专一于功能的地方.
好比我要实现一个短信处理平台,只须要最基本的短信收发便可. 例如登录网络银行,会受到短信、刷卡消费会受到短信、每日财经消息也会被发送到手机上等等.
那么试想一下,此处与手机的什么最相关?很显然,只有Message方法,所以这里使用接口会比较方便. 添加以下代码,运行便可.
Random r = new Random(); IMobilePhone[] nks = new IMobilePhone[100]; for(int i = 0; i < nks.Length; i++) { if(r.Next(2) == 1) { nks[i] = new Nokia1280(); } else { nks[i] = new Nokia_Lumia_900(); } } foreach(IMobilePhone n in nks) { n.Message(); Console.WriteLine(); }
注:这里只针对一个Message方法,与什么手机没有任何关系,100元的通常手机能够实现,好几千元的智能手机也能实现,所以不用考虑那么复杂,使用接口变得更加容易. 由于此处面向的是功能,是一种能力.
面向运行软件能力的应用
再来看一个关于智能机应用的例子.
好比我如今要作一个网络交友平台,这个显然是针对智能机设定的一个功能,假定全部手机都已经安装完成客户端,我如今的应用只针对用户发来的数据包,即只负责与用户进行数据的交互. 那么彻底没有必要针对全部智能手机这个对象,只须要使用RunApp方法便可,那么此处使用接口是再好不过的了.
综上讨论,能够得出结论了,对于使用逻辑上,接口与抽象类到底用哪个,在于你所写程序的关注点,你的项目中更关注与功能与方法,那么使用接口比较好;若果是关注一类对象,那就使用抽象类了. 因此说接口是对一类功能的抽象,抽象类是对一类对象的抽象.
同时不要忘记,对于结构的继承关系,只能使用接口.
固然再怎么用,也不会像这样来写代码的,实在是太复杂了.