接口

基本概念

  • 接口告诉类必须实现某些方法和属性
  • 如果没有实现,编译器会报错
  • 接口可以定义一个类中必须有的方法,这样一个类只要实现接口,就可以做特定的事情
  • 使用interface关键字定义接口
  • 接口不能添加任何字段,因为它不存储数据(但是可以包含属性
  • 命名规范:接口名称以I开头
  • 只需要在接口中添加方法名和参数,因为接口不做任何事情
  • 接口中的所有方法都是抽象方法,不能有方法体 ^bbfa07
  • 一个类只能继承一个类,但可以实现多个接口 ^018e94
  • 不可以实例化接口,不过可以引用接口
  • 接口就像清单,指出一个类可以做些什么事情

为什么使用接口?

  • 接口并不是为了避免重复代码,继承才是
  • 如果有一件事多个类都能实现,你需要这样一个类作为参数,但不希望继承时(比如你觉得根本用不到那些东西,不想让子类继承基类的方法和属性),这时你无法用继承中的基类来作为参数,那么使用接口,就可以知道:只要类实现了这个接口,它就能做这件事,可以作为参数使用,而不必知道它到底是什么类型
  • 如果有多件事一个类能实现它们,这时无法使用继承(只能继承一个类),就需要使用接口
  • 继承只是给类“分层”(减少重复),接口则可以给类“分类”(可以规定这个类可以干什么
  • 也就是说,当继承给你的类增加太多“负担”时,就应该考虑使用接口
  • 举个例子,假设你有一个电器类,很多类都继承了它,比如电脑、电视、冰箱……但是现在你想要用一个可以处理食物的类作为参数(比如面包机、烤箱、微波炉……),这时无法使用继承(因为你只能继承一个类),就需要编写一个可处理食物的接口,把要用到的方法属性放进去,在可处理食物的类中实现这个接口,把参数改成这样一个接口引用,就可以解决这个问题
  • C#拒绝继承多个类,并通过接口提供保护,这是为了避免二义性
  • 二义性:当两个类分别继承一个基类,并且重写了基类中的同一个方法,这时如果允许继承多个类(这称为多重继承),那么继承这两个类的子类调用该方法(假设子类没有重写)时应该调用哪个方法?这就会出现问题:子类不知道应该调用哪个方法,因为继承了两个同名的不同方法
  • 这也被称作“致命的死亡菱形”(如果[[Csharp/Csharp读书笔记/Csharp读书笔记(一)-对象与引用#创建类图|画出类图]],会发现这像一个菱形)
  • 所以C#通过接口提供保护,接口并没有方法体(它不做任何事情),因为它没有方法体,所以一个类可以实现多个接口,即使接口中有同名方法,这个类也只能实现这一个方法,避免了二义性

接口引用

  • 接口引用与对象引用类似,可以保持一个类不被垃圾回收
  • 可以移动接口引用,就像移动标签一样
  • 一个类需要实现这个接口,才能添加该接口引用
  • 使用接口引用访问类时,只能访问该接口引用所对应的方法和属性 ^46c854

类与接口的强制转换

  • 可以使用is关键字来查看一个类是否实现了这个接口
  • 可以用as关键字来把一个类看成实现了这个接口(前提是使用is找出这个类确实实现了)
  • 为什么使用as:有时候,C#并不知道一个类实现了一个接口(使用了其他接口引用),所以使用is之后还要让C#认为它确实实现了
  • 举例: ^537c57
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//这里仅给出核心代码
IComputer[] computers=new IComputer[3];//假设有一个叫做IComputer的接口
computers[0]=new Windows();//假设Windows,Mac,Linux类实现了IComputer接口
computers[1]=new Mac();
computers[2]=new Linux();
...
for(int i=0;i<computers.Length;i++)
{
if(computers[i] is IServer){
IServer myserver;//IServer是一个假设的新接口
myserver=computers[i] as IServer;
//使用as,就能够让C#把这个对象看成实现了IServer,就可以使用IServer中的方法和属性了
myserver.Run();//假设IServer中有一个Run()方法,只有使用了as才能够使用
}
}
  • 在上面的例子中,C#知道computers数组中的类都实现了IComputer(因为这是个IComputer接口的数组),所以只能调用这些类中IComputer接口中包含的方法和属性(可以把接口看作一个清单,这里只能调用清单中的内容),而当使用is关键字找出这个类确实实现了IServer接口时,C#仍然不认为它实现了IServer(因为你根本没有告诉它这么认为,只是让他检查了这个“清单”是否与类匹配,这是is关键字所做的),所以你需要使用as关键字,这样C#就会认为它确实实现了IServer,就可以(且只能)调用IServer中的方法、属性了
  • 由子类代替基类时,这称为向上强制转换
  • 由基类代替子类时,这叫做向下强制转换
  • 接口也可以这么做(上面的举例就是一个接口向下强制转换的例子)

接口的继承

  • 接口可以继承其他接口
  • 实现继承其他接口的接口时,必须实现所有继承的接口

访问修饰符

  • public表示任何其他类都可以访问
  • private表示只有该类中的成员及该类的其他实例可以访问
  • protected表示对于该类的其他成员来说相当于private,而对于子类来说是public,也就是说子类成员可以访问子类成员及任何基类成员(注意:基类声明为protected后,无需使用base也可以访问基类中的公共类型)
  • internal表示只能从程序集或子类访问
  • sealed表示不能继承该类

抽象类

  • 抽象类不能实例化
  • 有时候,你想在一个类中完成一部分代码,在子类中再完成其余部分,于是基类根本不应该被实例化(它并不能工作——因为你没有完成它),这时应该使用抽象类,防止你不小心创建了本不应该创建的实例
  • 比如说,一个方法需要一个字段来计算,但是在基类中不想设置它的值,想在子类中设置,这时基类是不完整的,它无法计算,所以设置为抽象类,防止误实例化
  • 可以在抽象类中添加抽象方法,也可以有具体方法,这与接口不同

面向对象的设计原则

  • 继承:一个类或一个接口继承另一个
  • 抽象:建立一个类模型,首先时比较一般的类,然后继承的是更特殊的类,也就是由一般(基类)到特殊(子类)的过程
  • 封装:创建一个对象,使用私有字段在内部记录其状态,通过公共属性、方法使其他类只能使用需要的数据以防止错误
  • 多态:将一个对象用于需要其他对象的方法或语句,比如类与接口的强制转换