为什么我们需要接口

接口是指定一组函数成员而不实现它们的引用类型,只有类和结构可以实现接口。
接口是抽象类的进一步抽象。
可以很好的帮助我们解决类的耦合。
接下来我会一步步推出来为什么要使用接口。

抽象类解耦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
internal class Program {
static void Main(string[] args) {
HuaWei huaWei = new HuaWei();
Man man = new Man();
man.huaWei = huaWei;
}
}
class Man {
public HuaWei huaWei { get; set; }
}
class HuaWei {
public void Call() {
Console.WriteLine("我用华为打电话");
}
}

这个代码表示一个人拿了一部华为手机,当这个人想换成小米手机的时候,就不得不修改成一下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
internal class Program {
static void Main(string[] args) {
XiaoMi xiaoMi = new XiaoMi();
Man man = new Man();
man.xiaoMi = xiaoMi;
}
}
class Man {
public XiaoMi xiaoMi { get; set; }
}
class XiaoMi {
public void Call() {
Console.WriteLine("我用小米打电话");
}
}

有没有发现一个问题,这个人可能会换很多部不同的手机,难道我们每次都要重写Man类就为了让这个人打电话吗?当然不可能,所以我们想了一个方法,用抽象类封装一下,再通过多态来实现不就可以了吗:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
internal class Program {
static void Main(string[] args) {
Man man = new Man();
Phone phone = new XiaoMi();
phone.Call();
}
}
class Man {
public Phone phone { get; set; }
}
abstract class Phone {
public abstract void Call();
}
class XiaoMi:Phone {
override public void Call() {
Console.WriteLine("我用小米打电话");
}
}
class HuaWei:Phone {
override public void Call() {
Console.WriteLine("我用华为打电话");
}
}

这样就好多了,这下我们只需要每次给man换手机就行了,Call方法给每个手机厂商自己实现就行了。

接口解耦

当我们声明一个抽象类的时候,我们必须要写非常多的public abstract 用来声明抽象方法,还必须在子类中写好多override,况且随着手机的功能越来越多,手机可以当游戏机,可以当手电筒,可以当记事本,难道我们要在Phone中加入不属于手机初衷的功能吗?这样一点都不优雅,有没有优雅一点的办法?
有!就是使用接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
internal class Program {
static void Main(string[] args) {
Man man = new Man();
IPhone phone = new XiaoMi();
phone.Call();
}
}
class Man {
public IPhone phone { get; set; }
}
interface IPhone {
void Call();
}
class XiaoMi:IPhone {
public void Call() {
Console.WriteLine("我用小米打电话");
}
}
class HuaWei:IPhone {
public void Call() {
Console.WriteLine("我用华为打电话");
}
}

interface声明接口,接口内的成员隐式都是public abstract的所以无需再写也不能写了。还可以看到在实现接口的类中不用再写override了,优雅太优雅了。

声明接口的注意事项

接口成员不能是数据成员或静态成员
接口声明只能包含如下类型非静态成员函数声明:
方法
属性
事件
索引器
接口成员的声明不能包含任何实现代码,必须用分号代替每一个成员声明的主体。
按照惯例接口名称应以大写I开始。
接口声明可以分隔成分部接口声明。
接口声明可以有任何访问修饰符,但是接口成员都是隐式public的,不允许有任何修饰符包括public。
接口不能实例化,只能用来声明变量,但不能调用不属于这个接口成员的类成员。
接口可以继承一个或多个接口

实现接口

类和结构可以实现接口,而且可以实现多个接口。
实现接口的类或结构需要在基类列表中包含接口名称基类在前接口在后,必须为每一个接口成员提供实现。
如果一个类实现了多个接口,并且其中一些接口成员具有相同的签名和返回类型,那么类可以实现单个成员来满足所有包含重复成员的接口。
实现接口的类可以从它的基类继承实现的代码。

显式接口成员实现

使用限定接口名称来声明,由接口名称和成员名称以及它们中间的点分隔号构成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
internal class Program {
static void Main(string[] args) {
IKiller man1 = new Man();
man1.Kill();
IGoodMan man2 = new Man();
man2.Smile();
}
}
interface IKiller {
void Kill();
}
interface IGoodMan {
void Smile();
}
class Man : IKiller, IGoodMan {
void IKiller.Kill() {
Console.WriteLine("我是个杀手");
}

void IGoodMan.Smile() {
Console.WriteLine("我是个好人");
}
}

显式实现接口成员的函数不应该有访问修饰符,因为其是隐式public的。
显式接口成员实现只可以通过指向接口的引用来访问,其他的类成员都不可以直接访问他们。
由于其他类成员不能直接访问显式接口成员的实现,派生类的成员也不能直接访问他们,他们必须总是通过接口的引用来访问。