基础系列(9)—— 抽象方法和接口
最近一直在复习关于c#的知识点,发现里面有好多重要的内容(也是自己以前不懂的地方),特别是面向对象的部分,同时又是我们学习中的重点和难点,抽象类的接口又是多态的基础(也是实现手段),所以今天在这里对接口和抽象类做一下总结,拿出来和大家分享一下,让我们共同的交流学习与进步吧!
一 抽象类
(一)什么是抽象类和抽象方法?
(1)抽象类的定义:简单的理解就是描述共性的类,抽象类中不考虑具体的实现,只是定义具有共性且必须实现的方法(也就是抽象方法),其中声明时用abstract关键字。
(2)重点解释:抽象类是特殊的类,只是不能被实例化;除此以外,具有类的其他特性;重要的是抽象类可以包括抽象方法,这是普通类所不能的;抽象类可以派生自一个抽象类,可以覆盖基类的抽象方法也可以不覆盖,如果不覆盖,则其派生类必须覆盖它们。
(3)抽象方法的定义:定义在抽象类里面的方法,并且是没有方法内容的,其中只有一个方法名和参数列表的方法。并以;结尾。为了标注它的与众不同,在它的返回类型前加abstract。
(4)重点解释:抽象方法声明在抽象类里面,是派生类的行为的一个抽象,里面没有具体的方法的实现,其中声明时也用abstract关键字。
(5)现实例子:如老师的具体任务是教学,具体教什么内容,如何教,每个老师都有自己独特的方法和技巧,对教学的这个动作就可以提取出来作为抽象的方法,而不实现里面的具体教学内容,这个实现,是有具体的任课老师用自己的授课方式去实现。
(二)抽象类和抽象方法的实现方式
接下来,我们直接看一个代码,在代码中讲解会比较好一点。
题目需求是:设计一个立方体的抽象类,从而求出正方体,长方体的体积和表面积、
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace 抽象类 { //抽象类的声明,实现,调用的演示 public abstract class AreaAndVolume { public abstract double GetAread();//抽象方法,没有方法的具体实现,以;结束 double GetVolume() //非抽象方法,可以包含方法的具体实现 { return 0; } public int age; //这里并没有报错 public AreaAndVolume ();//可以包含构造函数 } //正方体类,继承抽象类,实现抽象类里面的具体行为 public class Cub : AreaAndVolume { double side; public Cub(double side) //构造函数,初始化side { this.side = side; } //通过重写抽象方法,从而实现具体的求表面积的行为 public override double GetAread() { return side * side * 6; } //非抽象方法,正常方式实现即可 public double GetVolume() { return side * side * side; } } //长方体类的具体实现方式与正方体的一样 public class Cubeid : AreaAndVolume { public double length; public double weight; public double height; public Cubeid(double length, double weight, double height) { this.length = length; this.height = height; this.weight = weight; } public override double GetAread() { return ((length * weight) + (length * height) + (weight * height)) * 2; } public double GetVolume() { return height * weight * length; } } class Program { static void Main(string[] args) { Cub c1 = new Cub(3); //实例化对象,通过构造函数赋值 Cubeid cu1 = new Cubeid(2, 5, 6); //方法的具体调用 Console.WriteLine("正方体的表面积是:{0},体积是:{1}", c1.GetAread(), c1.GetVolume()); Console.WriteLine("长方体的表面积{0},体积是:{1}", cu1.GetAread(), cu1.GetVolume()); Console.ReadLine(); } } }
运行结果:
(三)抽象类的特性
(1)抽象类不能被实例化
(2)抽象类可以包含抽象方法和抽象访问器,可以包含构造函数和实例变量。
(3)不能用sealed修饰符修改抽象类,因为抽象类本身就是用来给其他类继承的
(4)抽象类的非抽象子类必须实现其继承的所有抽象方法和抽象访问器
(四)抽象类的构造函数
(1)不要再抽象类中定义public或protected internal访问权限的构造函数
(2)应在抽象类中定义protected或private访问权限的构造函数
(3)如果在抽象类中定义一个protected构造函数,则在实例化派生类时,基类可以执行初始化任务
(五)抽象方法的注意事项
(1)抽象方法是隐式的虚方法,其实就是用abstract关键字修饰
(2)抽象方法只允许声明在抽象类中
(3)抽象方法不能提供实际的实现,所以没有方法体;抽象方法的实现是在非抽象的派生类中以override重写实现的
(4)抽象方法声明中不可以使用static或者virtual修饰符
(5)abstract关键字不能修饰静态方法或静态属性
二 接口
抽象类的讲解先告一段落,我们接下来讲解和抽象类比较像的接口
(一)什么是接口
(1)接口的定义: 用来定义一种程序的协定,可以定义属于任何类或结构的一组相关行为。
(2)重点理解:实现接口的类或者结构要与接口的定义严格一致。有了这个协定,就可以抛开编程语言的限制(理论上)。C#接口可以从多个基接口继承,而类或结构可以实现多个接口。C#接口可以包含方法、属性、事件和索引器,接口中不能包含常数,字段,运算符,实例构造函数和析构函数。接口本身不提供它所定义的成员的实现。接口只指定实现该接口的类或接口必须提供的成员。
(二)接口的实现过程
我们用接口的形式重新实现上面的一个例子:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 接口与抽象类
{
//接口的声明,实现,调用的演示
public interface AreaAndVolume //声明一个接口,里面有求表面积和体积的方法
{
public virtual void run();//声明虚方法,并没有出错
double GetAread();
double GetVolume();
}
public class Cub : AreaAndVolume//具体的正方体类,实现接口里面的方法
{
double side;
public Cub(double side)
{
this.side = side;
}
public double GetAread()
{
return side * side * 6;
}
public double GetVolume()
{
return side * side * side;
}
}
public class Cubeid : AreaAndVolume
{
public double length;
public double weight;
public double height;
public Cubeid(double length, double weight, double height)
{
this.length = length;
this.height = height;
this.weight = weight;
}
public double GetAread()
{
return ((length * weight) + (length * height) + (weight * height)) * 2;
}
public double GetVolume()
{
return height*weight*length;
}
}
//接口的继承演示
public interface A
{
void Test1();
}
public interface B:A
{
void Test2();
}
public interface C : B
{
void Test3();
}
public class MyClass:C
{
public void Test1()
{
Console.WriteLine("这是第一个接口方法的实现");
}
public void Test2()
{
Console.WriteLine("这是第二个接口方法的实现");
}
public void Test3()
{
Console.WriteLine("这是第三个接口方法的实现");
}
}
class Program
{
static void Main(string[] args)
{
//接口的调用演示
Cub c1=new Cub (5);
Cubeid cu1 = new Cubeid(2,5,6);
Console.WriteLine("正方体的表面积是:{0},体积是:{1}",c1.GetAread(),c1.GetVolume());
Console.WriteLine("长方体的表面积{0},体积是:{1}",cu1.GetAread(),cu1.GetVolume());
//接口的继承演示
MyClass mc = new MyClass();
mc.Test3();
mc.Test2();
mc.Test1();
Console.ReadLine();
}
}
}
运行结果:
(三)接口的注意事项:
1、对象不能实例化;
2、C#接口只可以包含方法、属性、事件和索引器,且这些成员定义为公有的
3、接口必须在具体类里面实现所有成员
4、一个类可以直接继承多个接口(允许多继承),但只能直接继承一个类(包括抽象类)。
5、接口之间的继承,可以使一个接口继承另外一个接口,且必须实现接口里面的所有方法
三 接口与抽象方法的比较
(一)相同点:
1、不能实例化;
2、包含未实现的方法声明;
3、派生类必须实现未实现的方法,抽象类是抽象方法,接口则是所有成员(不仅是方法包括其他成员);
(二)区别;
1.类是对对象的抽象,可以把抽象类理解为把类当作对象(类对象),抽象成的类叫做抽象类.而接口只是一个行为的规范或规定
2.接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法;
3.一个类一次可以实现若干个接口,但是只能扩展(继承)一个父类
4.接口可以用于支持回调,而继承并不具备这个特点.
5.抽象类不能被密封。
6.抽象类实现的具体方法默认为虚的(通过重载实现具体的行为),但实现接口的类中的接口方法却默认为非虚的,当然您也可以声明为虚的(接口里面可以声明虚方法).
7.(接口)与非抽象类类似,抽象类也必须为在该类的基类列表中列出的接口的所有成员提供它自己的实现。但是,允许抽象类将接口方法映射到抽象方法上。
8.抽象类实现了oop中的一个原则,把可变的与不可变的分离。抽象类和接口就是定义为不可变的,而把可变的座位子类去实现。
9.好的接口定义应该是具有专一功能性的,而不是多功能的,否则造成接口污染。如果一个类只是实现了这个接口的中一个功能,而不得不去实现接口中的其他方法,就叫接口污染。
10.尽量避免使用继承来实现组建功能,而是使用黑箱复用,即对象组合。因为继承的层次增多,造成最直接的后果就是当你调用这个类群中某一类,就必须把他们全部加载到栈中!后果可想而知.(结合堆栈原理理解)。同时,有心的朋友可以留意到微软在构建一个类时,很多时候用到了对象组合的方法。比如asp.net中,Page类,有Server Request等属性,但其实他们都是某个类的对象。使用Page类的这个对象来调用另外的类的方法和属性,这个是非常基本的一个设计原则。
11.如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法.
三 接口与抽象方法的使用场景
1.如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单的方法来控制组件版本。
2.如果创建的功能将在大范围的全异对象间使用,则使用接口。如果要设计小而简练的功能块,则使用接口。
3.如果要设计大的功能单元,则使用抽象类.如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。
4.抽象类主要用于关系密切的对象;而接口适合为不相关的类提供通用功能。
今天总结的比较多,这些内容也比较重要,我们需要认真理解并且实践,由于自身也是“小白”,欢迎大家批评指正,谢谢大家!
参考资料:《c#程序设计》陈语林 中国水利水电出版社
《c#从入门到精通》人民邮电出版社