接口和抽象类是面向对象设计中最精妙最关键的部分。
学编程的重点:不是学,而是用。
抽象类:
函数成员没有被完全实现的类。(没有函数体。)
abstract class Student
{
//只有返回值、函数名和参数列表,没有方法体。完全没有被实现的方法,也就是抽象方法。
public abstract void Study();
// 一个类一旦有了抽象方法或者其他抽象成员,这个类就是抽象类。
}
abstract成员不能是private。
因为抽象类的子类去实现抽象类的方法,如果子类都没法访问,就更没法实现了,所以abstract成员不能是private。
编译器不允许实例化一个抽象类。
抽象类核心概念:因为抽象类含有未被实现的函数成员。
如果不能实例化这个抽象类,那么抽象类的功能:抽象类是专门为做基类而生的。
- 作为基类,派生其他子类。在派生类中把没有实现的函数成员,通过override重写实现了。
- 抽象类作为基类,可以用来声明变量。用基类类型的变量去引用子类类型的实例。
抽象类唯一能做的事是专门为做基类而生的。
开闭原则:
我们应该封装一些固定,不变的,稳定的、确定的成员;把不确定的,有可能改变的成员抽象为抽象成员、抽象类,留给子类去实现。
抽象类和开闭原则天生就是一对。
演化过程:抽象类和接口怎么来的?
第一步:初始化与复制
我买了一辆车Car,这车能跑Run能停Stop。后来我又买了一辆卡车Truck,卡车也可以跑和停。直接把方法粘贴过去,把Car改为Truck。代码如下:
class Car
{
public void Run()
{
Console.WriteLine("Car is running...");
}
public void Stop()
{
Console.WriteLine("Stopped");
}
}
class Truck
{
public void Run()
{
Console.WriteLine("Truck is running...");
}
public void Stop()
{
Console.WriteLine("Stopped");
}
}
此时就违反了设计的原则,不能copy paste。改进。
第二步:抽象与提取。
stop方法两者都是共有的,这时我们想可以提取出来,找(抽象出)一个两者的基类。Vehicle。
class Vehicle
{
public void Stop()
{
Console.WriteLine("Stopped!");
}
}
class Car:Vehicle
{
public void Run()
{
Console.WriteLine("Car is running...");
}
}
class Truck:Vehicle
{
public void Run()
{
Console.WriteLine("Truck is running...");
}
}
此时有一个新的问题:如果我们用多态,去主函数里调用v点,成员访问符的时候。只能调用Stop,并不能调用Run。因为在Vehicle中并没有定义Run方法。一种新的解决方案是采用虚方法;另一种老的解决方案是:在Vehicle中也加上一个Run方法。
老方法如下:
class Vehicle
{
public void Stop()
{
Console.WriteLine("Stopped!");
}
public void Run(string type)
{
if (type == "car")
{
Console.WriteLine("Car is Running....");
}
else if (type=="truck")
{
Console.WriteLine("Truck is running....");
}
}
}
但是这样子就违反了我们的开闭原则:如果不是为了修改Bug或者添加新功能的话,否则不应该随便修改代码。类是关闭的,不能随意修改里面的代码,比如如果再买一辆赛车。
class Vehicle
{
public void Stop()
{
Console.WriteLine("Stopped!");
}
public void Run(string type)
{
if (type == "car")
{
Console.WriteLine("Car is Running....");
}
else if (type=="truck")
{
Console.WriteLine("Truck is running....");
}
else if (type=="race car")
{
Console.WriteLine("Race car is running....");
}
}
}
这样不断买新车,类作为一个封装关闭单元,就要不断新增代码。因此我们用我们的新方法,虚方法:virtual。让子类重写继承。
第三步:虚方法重写
class Program
{
static void Main(string[] args)
{
Vehicle v = new Car();
v.Run();
}
}
class Vehicle
{
public void Stop()
{
Console.WriteLine("Stopped!");
}
public virtual void Run()
{
Console.WriteLine("Vehicle is running...");
}
}
class Car:Vehicle
{
public override void Run()
{
Console.WriteLine("Car is running...");
}
}
class Truck:Vehicle
{
public override void Run()
{
Console.WriteLine("Truck is running...");
}
}
此时的问题:我们观察有时候Vehicle的Run方法其实没有太大的意义,纯粹就是为了抽象重写的。而且他自己永远也不会被调用到,概念特别模糊。那么我们想函数体里面就不用写任何东西。此时如果为空,那么感觉挺别扭的,那么我们干脆连{}花括号也别写了。此时没有方法体了,就不要叫virtual了,成了一个纯虚方法,此时给他一个新的名字:abstract。成员函数为abstract,那么class类也必须是abstract。至此抽象类,符合我们的开闭原则。
第四步:抽象类。
此时再有一个新车RaceCar进来,我们不用再回头动基类。这就符合了我们开闭原则。
补全代码方法:Rider智能提示补全:点击灯泡。选择补全缺失代码。
实现抽象方法时也要用override关键字。
abstract class Vehicle
{
public void Stop()
{
Console.WriteLine("Stopped!");
}
public abstract void Run();
}
class Car:Vehicle
{
public override void Run()
{
Console.WriteLine("Car is running...");
}
}
class Truck:Vehicle
{
public override void Run()
{
Console.WriteLine("Truck is running...");
}
}
class RaceCar:Vehicle
{
public override void Run()
{
Console.WriteLine("Race car is running...");
}
}
至此我们完成了所有满足开闭原则的抽象类的方法。
第五步: 接口
上例中,抽象类中只有一个函数是抽象方法。
问题:那么有没有一种可能:一个类中,所有的函数成员的都是抽象的?
在C#中纯虚类,其实就是接口interface。
而且子类因为要去实现接口中的抽象成员,接口表示本身就是抽象的,所以abstract和public就不用写了。
在C#中,定义接口,接口名称都是用字母I开头。
interface IVehicleBase
{
void Run();
void Fill();
void Stop();
}
接口子类中的override也不用写了。
如果子类中部分抽象方法不实现,那么需要将抽象方法前加上abstract来传递下去,待下一级子类去实现。(下推给下一子类实现。)
using System;
using System.Net.Mail;
using System.Security.Policy;
namespace AbstractClassAndOpenClosed
{
class Program
{
static void Main(string[] args)
{
Vehicle v = new Car();
v.Run();
}
}
interface IVehicle
{
void Stop();
void Fill();
void Run();
}
abstract class Vehicle:IVehicle
{
public void Stop()
{
Console.WriteLine("Stopped");
}
public void Fill()
{
Console.WriteLine("Pay and fill...");
}
abstract public void Run();
}
class Car:Vehicle
{
public override void Run()
{
Console.WriteLine("Car is running...");
}
}
class Truck:Vehicle
{
public override void Run()
{
Console.WriteLine("Truck is running...");
}
}
class RaceCar:Vehicle
{
public override void Run()
{
Console.WriteLine("Race car is running...");
}
}
}
Rider小技巧:按住Alt键可以选择多行。同时编辑。
什么是接口和抽象类
-
接口和抽象类都是“软件工程产物”。
- 具体类→抽象类→接口:越来越抽象,内部实现的东西越来越少。
- 抽象类是未完全实现逻辑的类(可以有字段和非public成员,它们代表了“具体逻辑”)。接口是完全未实现。
- 抽象类为复用reuse而生:专门为基类来使用,也具有解耦的功能。
- 封装确定的,开放不确定的,推迟到合适的子类当中去实现;
- 接口是完全未实现逻辑的“类”(“纯虚类”;只有函数成员;成员全部public,隐式public。不用写public);
- 接口视为解耦而生:“高内聚,低耦合”,方便单元测试。
- 解耦是一个“协约”,早已为工业生产所熟知(有分工必有协作,有协作必有协约)
- 接口和抽象类都不能被实例化,只能用来声明变量,引用具体类(concrete class)的实例。