抽象类的概念:在类中至少有一个函数成员没有被完全实现。
在c#中通过在一个类的前面加上abstract关键字来声明一个抽象类。
abstract class Animal
{
public double Weight{get;set;}
public void Run()
{
Console.WriteLine("I'm Running");
}
public abstract void Talk();
}
上面声明了一个Animal的抽象类,我们可以观察这个类的特点:
1.在声明这个类的时候前面加上了abstract关键字。
2.我们为这个类创建了一个weight的属性,实现了一个Run的方法,但是最后一个Talk方法时没有实现的,并且在前面加上了abstract关键字,我们结合抽象类的概念:在类中至少有一个函数成员没有被完全实现。如果我们把最后一个方法实现了,那这就不是一个抽象类了。
3.我们继续进行尝试:我们是否可以将abstract关键字应用于字段或者属性上呢?上面我们使用public关键字进行修饰方法,其他的关键字是否也可以应用于抽象类中呢?
abstract class Animal
{
private string name;
//public abstract string name;报错的
public abstract double Weight{get;set;}
//private abstract double Weight{get;set;}报错的
public void Run()
{
Console.WriteLine("I'm Running");
}
protected abstract void Talk();
}
4.经历了一系列的尝试,我们发现,在抽象类中声明字段是不能够添加abstract关键字的,在抽象类中能够添加abstract关键字只有属性和方法,并且添加abstract关键字的属性和方法是不能使用private修饰。这也很好理解,如果一个方法添加abstract关键字,那被子类继承后一定是会被重写的,如果添加private关键字,那么子类就不能访问这个方法,自然无法重写这个方法,所以抽象类中不允许添加abstract关键字的属性或者方法使用private修饰。
下面我们通过实例来理解在c#中如何使用抽象类
class Student
{
public string name{get;set;}
public void Run()
{
Console.WriteLine("I'm Running");
}
public void Talk()
{
Console.WriteLine("I'm a Student");
}
public void Learn()
{
Console.WriteLine("I'm Studying");
}
}
class Teacher
{
public string name{get;set;}
public void Run()
{
Console.WriteLine("I'm Running");
}
public void Talk()
{
Console.WriteLine("I'm a Teacher");
}
public void Teach()
{
Console.WriteLine("I'm Teaching");
}
}
在设计这两个类的时候,我们发现了其中两个方法是相同的,为了减少copy、paste,我们可以为两个类提供一个基类。
class People
{
public string name { get; set; }
public void Run()
{
Console.WriteLine("I'm Running");
}
}
class Student : People
{
public void Talk()
{
Console.WriteLine("I'm a Student");
}
public void Learn()
{
Console.WriteLine("I'm Studying");
}
}
class Teacher : People
{
public void Talk()
{
Console.WriteLine("I'm a Teacher");
}
public void Teach()
{
Console.WriteLine("I'm Teaching");
}
}
这样做我们的类的设计层次就变得更加的清晰。我们知道在c#中可以使用父类的变量来接收子类的实例。
People p = new Student();
但是我们如何通过p这个变量来访问子类的方法呢?在c#的编译器中,他们都认变量的类型,即父类的变量都只能够调用父类的方法,而子类的方法是无法调用到的。或许我们可以通过在父类中添加和子类相同的方法来调用子类的方法?让我们试试看,在父类中添加Talk方法
class People
{
public string name { get; set; }
public void Run()
{
Console.WriteLine("I'm Running");
}
public void Talk()
{
Console.WriteLine("People is talking");
}
}
现在我们就可以调用Talk方法了,因为p引用的其实是一个Student类型的实例,所以我们期望他调用的方法也是Student中的方法。但他的结果却是:
事实总是不尽如人意,那么我们还能够尝试一些其他的解决方案。
class People
{
public string name { get; set; }
public void Run()
{
Console.WriteLine("I'm Running");
}
public void Talk(string type)
{
if (type == "student")
Console.WriteLine("I'm a Student");
else if (type == "teacher")
Console.WriteLine("I'm a Teacher");
}
}
似乎在我们每一次调用这个方法的时候传入他对应类型字符串就行了,好像问题解决了,但真的是这样吗?如果我下一次又多添加一个类Driver,也是继承自People这个类,那么我们下次调用这个方法,如何才能访问到他的Talk方法呢?我们可以继续在这个方法的后面添加else if,不断增加条件不就行了?那每多增加一个类,就要修改一次这个方法里面的代码,这样子维护起来是相当麻烦了。所以说虽然这是一种解决方案,但是我们基本不会使用,所以这种写法必须被舍弃。
那我们还可以怎么解决呢?我们还可以利用类的三大特性之一的多态来实现我们的目的。修改后的People类如下:
class People
{
public string name { get; set; }
public void Run()
{
Console.WriteLine("I'm Running");
}
public virtual void Talk()
{
Console.WriteLine("I'm a People");
}
}
只要我们在子类中使用override重写这个方法,那么在调用这个方法的时候,这个方法就会执行到当前变量引用实例的最新版本(如果有多重继承,那么这个方法就会执行到override重写的最新一级),我们执行一下程序,观察结果:
发现得到了我们想要的结果,那么这确实是一种不错的解决方案。
我们继续思考,在People这个基类中,因为他在我们继承链的最顶端,所以我们调用Talk这个方法得到的结果是I'm a People,这确实是一个很大的概念了,通常我们对基类中方法调用的结果意义都不是很大。那么我们可不可以想在基类的方法中完全不做逻辑,那就变成了什么?不就是一个抽象类吗?只声明一个方法,而方法中不包含任何的逻辑。所以最后我们将代码优化成为终极版,如下:
abstract class People
{
public string name { get; set; }
public void Run()
{
Console.WriteLine("I'm Running");
}
public abstract void Talk();
}
class Student : People
{
public override void Talk()
{
Console.WriteLine("I'm a Student");
}
public void Learn()
{
Console.WriteLine("I'm Studying");
}
}
class Teacher : People
{
public override void Talk()
{
Console.WriteLine("I'm a Teacher");
}
public void Teach()
{
Console.WriteLine("I'm Teaching");
}
}
PS:
1.抽象类不能够实例化
2.“抽象类”就是为做基类而生的
3.抽象类的设计应当遵循开放/关闭原则
4.抽象类可以继承抽象类,并且抽象类可以使纯抽象类(接口)