這一篇主要來(lái)解析關(guān)于面向?qū)ο笾凶羁傄囊粋€(gè)概念——接口。
對(duì)于接口來(lái)說(shuō),C#是有規(guī)定使用Interface關(guān)鍵字來(lái)聲明接口。它的聲明是和類一致的??梢哉f(shuō)接口就是一個(gè)特殊的抽象類。如下代碼:
class Program
{
static void Main(string[] args)
{
}
}
//聲明一個(gè)可以飛的接口
interface IRunable
{
//包含可以被繼承的子類實(shí)現(xiàn)的方法
void Run();
}
由以前的抽象類的知識(shí)可以知道,抽象類是沒(méi)有辦法實(shí)例化的(因?yàn)楹谐橄蟪蓡T,而抽象成員不含有方法體)。那么接口可不可以實(shí)例化呢?答案是肯定的,不能實(shí)例化??聪旅娴囊欢未a:
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/8.1.png" alt="" />
這個(gè)時(shí)候編譯器告訴我們無(wú)法創(chuàng)建抽象類或者接口的實(shí)例。
二,接口可以定義哪些成員
1)接口就是一個(gè)定義"具有某種能力的抽象類",既然接口是類,那么它的內(nèi)部可以定義哪些成員呢?
首先,在普通的類中,可以有字段,屬性,方法,索引器,抽象方法等等。那么接口呢?
看下面直接聲明字段,編譯器會(huì)報(bào)錯(cuò),告訴我們接口內(nèi)不能聲明字段
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/8.2.png" alt="" />
既然接口內(nèi)不能有字段,那也就不存在封裝字段了。所以上邊圖示的封裝字段的代碼也是錯(cuò)誤的。
同理由上面的代碼也可以知道,在接口中是不可以定義顯式的屬性(因?yàn)樵趯傩灾幸僮髯侄钨x值,但是字段沒(méi)有辦法在接口中聲明)。
那么接口可以聲明自動(dòng)屬性么?看下面的代碼:
//聲明一個(gè)可以飛的接口
interface IRunable
{
//聲明字段
int nAge { get; set; }
string strName { get; set; }
////包含可以被繼承的子類實(shí)現(xiàn)的方法
void Run();
}
代碼可以順利編譯通過(guò),那么是為什么呢?這就要看.NET的源碼,我把源碼編譯后的比較結(jié)果如下圖:
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/8.3.png" alt="" />
抽象方法就不用多了,本來(lái)接口就是一個(gè)抽象愛(ài)類,當(dāng)然可以定義抽象類,但是不在使用abstract關(guān)鍵字,而且方法必須沒(méi)有方法體;
2)繼承接口的子類必須實(shí)現(xiàn)接口的所有抽象成員。
我們先來(lái)看下面的代碼:
//聲明一個(gè)接口,其中包含屬性和未實(shí)現(xiàn)方法void
interface IRunable
{
string strName { get; set; }
void Run();
}
下面來(lái)一個(gè)實(shí)現(xiàn)類,如下:
class Person:IRunable
{
public void Run()
{
Console.WriteLine("我可以奔跑!");
}
}
這時(shí)候,我們編譯,編譯器會(huì)告訴我們什么呢?如下圖:
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/8.4.png" alt="" />
所以繼承接口的類,必須實(shí)現(xiàn)接口的所有抽象成員。
正確的代碼如下:
class Person:IRunable
{
public void Run()
{
Console.WriteLine("我可以奔跑!");
}
public string strName
{
get
{
return strName;
}
set
{
strName = value;
}
}
}
通過(guò)以上的代碼可以發(fā)現(xiàn):
①我們的繼承類在實(shí)現(xiàn)接口成員的時(shí)候不需要使用override關(guān)鍵字
②實(shí)現(xiàn)接口的時(shí)候,必須保持簽名一致
由前面抽象類的知識(shí)我們有沒(méi)有這樣的疑問(wèn),什么時(shí)候使用抽象類,什么時(shí)候使用接口呢?
總結(jié)如下:
①使用抽象類:可以找到父類,并且希望通過(guò)父類繼承給子類一些成員
②使用接口:接口就是一個(gè)純粹的為了規(guī)范實(shí)現(xiàn)的類。比如:多個(gè)類具有相同的方法,但是卻找不到父類,就可以將方法定義在接口中。讓這些類去實(shí)現(xiàn)。
下面糾紛別來(lái)看兩端代碼,比較抽象類和接口的異同,首先是抽象類:
class Program
{
static void Main(string[] args)
{
Student s = new Student();
//Student類通過(guò)繼承獲得NAge屬性
s.NAge = 10;
s.Eat();
Console.WriteLine("--------Student和Worker類分別通過(guò)繼承獲得了父類的非私有成員,實(shí)現(xiàn)了父類的抽象方法--------");
Worker w = new Worker();
//Worker類通過(guò)繼承獲得NAge屬性
w.NAge = 40;
w.Eat();
Console.ReadKey();
}
}
//定義父類
abstract class Person
{
private int nAge;
public int NAge
{
get { return nAge; }
set { nAge = value; }
}
private void Run()
{
Console.WriteLine("我是父類,我可以跑!");
}
public abstract void Eat();
}
class Student : Person
{
//子類覆寫(xiě)了父類的抽象方法
public override void Eat()
{
Console.WriteLine("我是子類,我繼承了父類,我可以在學(xué)校吃飯!");
}
}
class Worker:Person
{
//同樣Worker也通過(guò)繼承獲得了父類的非私有成員
public override void Eat()
{
Console.WriteLine("我是子類,我繼承父類,我可以在工廠吃飯");
}
}
接下來(lái),來(lái)看看接口是怎么規(guī)范多個(gè)類的實(shí)現(xiàn)的。
class Program
{
static void Main(string[] args)
{
Student s = new Student();
s.strName = "小學(xué)生";
s.Run();
Console.WriteLine(s.strName);
Console.WriteLine("--------------------");
Worker w = new Worker();
w.strName = "看我能不能瀆職";
w.Run();
Console.WriteLine(w.strName);
Console.ReadKey();
}
}
interface IRunable
{
//規(guī)范子類必須實(shí)現(xiàn)strName屬性
string strName { get; set; }
//規(guī)范子類必須實(shí)現(xiàn)Run()方法
void Run();
}
class Student:IRunable
{
//這里是子類的字段
string strname;
public string strName
{
get
{
return strname;
}
set
{
strname = value;
}
}
public void Run()
{
Console.WriteLine("我是小學(xué)生,我在學(xué)校里面跑步!");
}
}
class Worker:IRunable
{
string strname;
public string strName
{
get
{
return "工人";
}
set
{
strname = value;
}
}
public void Run()
{
Console.WriteLine( "我是工人,我需要在廠區(qū)跑!");
}
}
由以上的代碼可不可以發(fā)現(xiàn),接口僅僅在規(guī)定一個(gè)規(guī)范子類的實(shí)現(xiàn),而抽象類可以通過(guò)繼承,繼承給子類某些成員。
最后來(lái)看一下,接口的顯示實(shí)現(xiàn),我先看接口的普通實(shí)現(xiàn)(以上的代碼實(shí)現(xiàn)接口的方式都是隱式實(shí)現(xiàn))
interface IRunable
{
//規(guī)范子類必須實(shí)現(xiàn)strName屬性
string strName { get; set; }
//規(guī)范子類必須實(shí)現(xiàn)Run()方法
void Run();
}
class Student:IRunable
{
//這里是子類的字段
string strname;
public string strName
{
get
{
return strname;
}
set
{
strname = value;
}
}
public void Run()
{
Console.WriteLine("我是小學(xué)生,我在學(xué)校里面跑步!");
}
}
顯式實(shí)現(xiàn)接口
class Student:IRunable
{
//這里是子類的字段
string strname;
//顯示實(shí)現(xiàn)接口
string IRunable.strName
{
get
{
return strname;
}
set
{
strname = value;
}
}
void IRunable.Run()
{
Console.WriteLine("我是小學(xué)生,我在學(xué)校里面跑步!");
}
}
顯示的實(shí)現(xiàn)接口是為了解決方法名沖突的問(wèn)題。但是顯示實(shí)現(xiàn)接口會(huì)出現(xiàn),在上面的代碼中會(huì)出現(xiàn)一個(gè)問(wèn)題,如下圖:
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/8.5.png" alt="" />
為什么會(huì)這樣呢?
因?yàn)轱@式實(shí)現(xiàn)接口的方法是私有的,不能通過(guò)對(duì)象變量來(lái)調(diào)用。那應(yīng)該怎么調(diào)用呢,看下面的代碼:
class Program
{
static void Main(string[] args)
{
//里氏替換原則,父類變量指向子類對(duì)象,并通過(guò)父類變量調(diào)用子類方法
IRunable ir = new Student();
ir.Run();
Console.ReadKey();
}
}
interface IRunable
{
//規(guī)范子類必須實(shí)現(xiàn)strName屬性
string strName { get; set; }
//規(guī)范子類必須實(shí)現(xiàn)Run()方法
void Run();
}
class Student:IRunable
{
//這里是子類的字段
string strname;
//顯示實(shí)現(xiàn)接口
string IRunable.strName
{
get
{
return strname;
}
set
{
strname = value;
}
}
void IRunable.Run()
{
Console.WriteLine("我是小學(xué)生,我在學(xué)校里面跑步!");
}
// Student s = new Student();
}
打印結(jié)果如下:
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/8.6.png" alt="" />
顯式實(shí)現(xiàn)接口,這個(gè)接口的方法,只能通過(guò)接口變量來(lái)調(diào)用。
接口導(dǎo)圖總結(jié)如下:
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/8.7.png" alt="" />