抽象类及接口详解
一、前言
在上一节中我们讲到抽象类和接口之间的异同,我们一起回顾下其异同。
同:
1、都不可以被实例化
2、都含有声明但未实现的方法
3、都可以被继承
4、其子类必须实现其声明未实现的方法
异:
1、接口是多继承,抽象类是单继承(一个类仅能继承一个抽象类,但可以继承多个接口)
2、抽象类可以包含实现的方法,接口不能包含实现的方法
3、接口支持回调,抽象类不支持
4、抽象类更多的定义在一些类关系紧密的类间,接口则定义在实现其某一种功能之间
抽象类和接口的异同我们再次熟悉了一遍,今天我们主要讲的是抽象类和接口使用场景及详讲抽象类的使用方法及接口的使用方法
二、使用场景
抽象类、普通类、接口。我们到底什么时候使用哪一个呢?这就很头痛了,不是一直使用一个就是好的。每一个都有每一个的使用场景。下面我们看看到底啥事时候用啥东西吧。
我们现在假设一个场景,现在需要设计一个程序,用来描述各个动物的一些生活习性,这里我们就有猪、猫、狗。
不使用抽象类也不使用接口。我们设计他们三个的各个习性,就是猪的类里面就包含自己的习性,猫类里面包含自己的习性,狗类里面包含自己的习性。如果各个习性较多但也有相同的。这样的类看起来非常的冗余。
我们加入抽象类(设计实现大的功能单元),定义一个抽象类-哺乳动物类,其中定义了共同的习性,走路的方法,呼吸的方法,繁衍下一代的方法。但是叫声不一样。我们又声明一个叫声的方法不实现(抽象方法)。这样再我们去定义猪类或者狗类的时候只需要写出不一样的地方即可。这样看起来代码也简洁,清楚
我们现在改用接口(设计实现小而简练的功能),我们把这些动物可以做什么列出来,然后统一使用接口去定义公共的。比如叫声,行走。这些功能,我们就可以使用接口来定义声明。然后继承再去实现。
到了这里,我们总结下到底我们编写程序为什么需要使用抽象类呢?为什么需要使用接口呢?单一用一个普通的类不好吗?简单又容易。其实不然。存在即合理。我们一起看看到底为什么要使用吧
为什么使用抽象类?
抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象`。这样来说如果一个类设计就是为了给其他类继承的,它代表一类对象所具有的公共属性或方法,我们就使用抽象类。就像上门的这个例子,把一些动物共同的属性或者方法提取出来,定义一个抽象类。代码变的易懂,代码冗余减少,变得简洁明了。实现了代码复用性。
那么为什么使用接口呢?
通俗点讲就是为了降低代码的耦合度。接口的意义在于抽象,不拘细节,从而使同类事物在在同一高度具有通用性及可替代性。上面的例子来讲,规定好了这个叫声这个方法,那么继承的就去实现这个叫声方法就好了。如果某天加入了一个新的动物,这样我们也不需要修改其他的任何方面,仅继承一下接口修改本身即可,不需要修改或改变其他的类或者接口。系统的灵活性增加了。
这里可能会有个问题了。既然有了抽象类为什么还要用接口呢?这会不会有点多余?
答案肯定是不会的。那么有了抽象类为什么还要使用接口呢?我们看看抽象类和接口的异同就很快能明白了。
1、接口提供的事统一的行为规范,供其他调用,而抽象类具有接口的特性同时还可以有自己的具体实现
2、抽象类只能有一个父类,可以实现多个接口
那么我们如何使用抽象类和接口呢?
三、抽象类及接口使用
就拿我们上面举的那个例子来编写一段代码:
/// <summary> /// 叫声的接口定义 /// </summary> public interface ICry { string Cry(); } /// <summary> /// 说话的接口定义 /// </summary> public interface ISay { string Say(); } /// <summary> /// 动物抽象的抽象类 /// </summary> public abstract class Animale { /// <summary> /// 包含的实现了的方法、呼吸、走路 /// </summary> /// <returns></returns> public static string Breathe() { return "呼吸一样"; } public static string Run() { return "走路一样"; } /// <summary> /// 未实现的抽象方法睡觉方法 /// </summary> /// <returns></returns> public abstract string Sleep(); } /// <summary> /// 普通的类 继承了抽象类及两个接口 /// </summary> public class Dog: Animale,ICry, ISay { /// <summary> /// 实现重写抽象方法睡觉 /// </summary> /// <returns></returns> public override string Sleep() { return "睡觉"; } /// <summary> /// 实现叫声接口方法 /// </summary> /// <returns></returns> public string Cry() { return "旺旺"; } /// <summary> /// 实现说话接口方法 /// </summary> /// <returns></returns> public string Say() { return "说话"; } }
在上面的代码中,我们列举出了接口的定义及抽象类、抽象方法的定义及使用。在最下面一个普通类中,我们继承了一个抽象类及两个接口,可以实现多个接口但是只能有一个抽象父类。如果继承两个抽象类的话会报错的。
抽象类关键字--abstract
接口关键字--interface
四、扩展延伸(密封类)
讲到抽象类,我们也可以一起看看密封类,密封类不能作为基类,禁止派生。如果重写了某些功能会导致编译错误或者为了防止第三方进行扩展重写,这个时候我们就可以使用到密封类。
重点注意:
1、密封类中不能包含虚方法(Virtual)和抽象方法(abstract)。因为密封类是不能被继承的也就没有派生类,就不具备实现抽象方法和虚方法的机会。
2、在使用密封类(sealed)的时候,密封类将限制它的使用,现在及未来都将受到影响
3、如果实例方法包含了sealed修饰符,那么它也必须包含override修饰符,其父类方法必须包含virtual修饰符
/// <summary> /// 普通动物类 /// </summary> public class Animals { /// <summary> /// 动物的叫声方法,因为其派生类重写了次方法,所以必须使用virtual修饰符 /// </summary> /// <returns></returns> public virtual string Cry() { return "叫声"; } } /// <summary> /// 动物狗类密封类,无法产生派生类,不能作为基类,继承了动物类 /// </summary> public sealed class Dog : Animals { /// <summary> /// 重写了动物叫的方法,同时标记为了密封方法 /// </summary> /// <returns></returns> public override sealed string Cry() { return "旺旺"; } }
在使用密封类的时候我们需要考虑的因素需要更加的全面,更加的谨慎,以防后面重新推翻重写。每个东西都没有绝对的好,只有你用的恰到好处。多思考多选择才是智者。
总结
欢迎大家扫描下方二维码,和我一起学习更多的C#知识