abstract(抽象)
//要在类中声明abstract方法,类上就必须加abstract internal abstract class Person { //abstract修饰的方法不能有方法体! internal abstract void Eat(); }
由abstract定义的方法叫做抽象方法,抽象方法只能放置在被abstract修饰的抽象类中。抽象类有一个非常明显的特点:
不能被实例化!
但是,它除了可以声明抽象方法,还可以声明其他普通类成员,包括构造函数。
因为抽象方法没有方法体,所以凡是继承自抽象方法的类,都要通过override实现抽象类的全部抽象方法。而且,override的方法体内,不能通过base调用其抽象类方法。
演示:
抽象类还算是一个类,但另外一个东东,就走得更远了:
接口(interface)
和抽象类相比,它内部只能有声明(方法和属性),不允许有任何实现:
//接口类型上可以加访问修饰符 internal interface ILearn //接口通常加“I”作为前缀 { //一些方法定义都是可行的: //注意在接口中这不是自动属性,它仍然需要在子类中实现 int Score { get; set; } //最常用的方法声明 void Practise(); int AttendExam(string major); void GoClass(); //不允许有字段 //int score; //不能有方法体(实现),哪怕“空”内容也不行 //void Practise() { } //不能有访问修饰符,接口成员默认public //public int AttendExam(string major); //也不要有其他访问修饰符,都是画蛇添足 //abstract void GoClass(); }
另外注意几个语法点:
- 接口一样不能被实例化(所以也不能有构造函数)
- 接口可以也只可以继承接口
- 接口可以被多重继承
interface IYuanzhanLearn : ILearn //接口只能继承接口 { void Coach(); }
一个子类可以同时继承一个父类,再实现若干个接口。
一个子类要实现一个接口,就和继承一个抽象方法一样,必须实现接口的全部成员!全部成员还包括接口继承的所有接口里面的成员。
internal class Student : //子类只能继承一个父类(Person), //但可以继承多个接口 //还可以继承一个父类加多个接口 //但是,父类必须放在最前面 Person, IYuanzhanLearn, IPlay { ///需要实现IYuanzhanLearn ///以及IYuanzhanLearn继承的ILearn ///和IPlay的成员 }
在子类中
实现接口
有两种方式。绝大多数时候,我们都使用:
隐式实现,就是在子类中添加和接口中定义相同的方法:
internal class Student : Person, ILearn { //ILearn中定义了一个Practise()方法 public void Practise() { //实现了ILearn中定义的Practise()方法 Console.WriteLine("键盘敲烂,月薪过万!"); } }
这时候Practise()方法就像一个普通方法一样,既可以被Student变量调用,也可以被ILearn变量调用:
Student pzq = new Student(); pzq.Practise(); //也可以: //ILearn pzq = new Student(); //pzq.Practise();
只有当一个子类继承了多个接口,且多个接口中定义了相同方法的时候,我们才使用接口的
显式实现。比如我们的IPlay接口,也定义了一个Practise()方法:
internal interface IPlay { void Practise(); }
现在就不能在Student中隐式实现Practise()了,因为你一个Practise()方法不够两个Interface用啊!难道IPlay的实例和ILearn的实例用同样的方法实现么?
IPlay wx = new Student(); wx.Practise(); ILearn pzq = new Student(); pzq.Practise();
所以我们需要进行区分:
internal class Student : Person, ILearn, IPlay { //internal void ILearn.Practise() //不能有访问修饰符 void ILearn.Practise() //实现ILearn.Practise()方法 { Console.WriteLine("键盘敲烂,月薪过万!"); } void IPlay.Practise() //实现IPlay.Practise()方法 { Console.WriteLine("再撸一把啊!"); } }
这样就OK了,但是还要注意一点:显式实现的接口方法,不能用子类类型的变量进行调用:
Student pzq = new Student(); ///报错。因为: ///1. pzq是子类Student对象 ///2. Practise()是接口的显示实现 pzq.Practise();
最后,因为:
- 接口和抽象类,都不能实例化;
- 抽象方法和接口中的方法声明都不能有实现;
- 继承自抽象类和接口的子类,都必须实现抽象类的全部抽象方面和接口的全部定义成员
所以,一个常见的面试题就是:
接口和抽象类究竟有何区别,该如何选择使用?
最简单的回答是从语法层面来说,比如:
- 抽象类里除了抽象方法,还可以有其他实现;但接口不行,接口里面只能有成员声明
- 抽象类可以继承接口;但接口不能继承抽象类
- 抽象类只能作为父类使用,所以一个子类只能继承一个抽象类,但可以继承多个接口
- ……
但实际上,因为面向对象是代码管理的工具,我们更应该透过语法探究其所需要表达的意义:
抽象类还是一个类,类通常都被认为是对现实中事物的映射,是对事物的行为和状态的封装。所以,类名一般都是名词(比如Person、Animal、Major之类的),映射的是一个实体事物,其中既有方法(行为)也有字段(状态)。
而接口通常被认为是对行为(没有状态)的封装,所以接口名一般都是动词(比如IMove、ILearn),映射的是一些操作,其中只有方法(属性本质上也是一种方法)。
作业:
- 思考之前的Content类,该将其抽象成抽象类还是接口?为什么?并按你的想法实现。
- 一起帮里的求助总结、文章和意见建议,以及他们的评论,都有一个点赞(Agree)/踩(Disagree)的功能,赞和踩都会增减作者及评价者的帮帮点。能不能对其进行抽象?如何实现?
- 引入两个子类EmailMessage和DBMessage,和他们继承的接口ISendMessage(含Send()方法声明),用Console.WriteLine()实现Send()。
- 一起帮还可以在好友间发私信,所有又有了IChat接口,其中也有一个Send()方法声明。假设User类同时继承了ISendMessage和IChat,如何处理?