接口与抽象类的重要性
- 描述接口和抽象类的区别和用法不同是面试必考的一些内容
- 在日常工作中,若不能熟练驾驭接口和抽象类的使用,则写出的代码很可能比较难测试,比较难维护
- 接口和抽象类是面向对象设计中最关键、最精妙的部分,没有它们就没有现代面向对象设计和现代软件工程,是软件工业设计的两块基石,它们也是所有高阶面向对象设计的起点
关于设计模式
- 学习设计模式需要两个前提:
- 必须已透彻理解何为接口和抽象类,并在实际项目中熟练使用
- 必须已深入了解SOLID设计原则,并能在日常工作中自觉地使用他们
- 想具备这两个条件,需在一个有质量文化的团队中踏实地写上两三年的代码
- 算法、设计原则、设计模式等存在不少为了好看,而是为了解决实际应用中的问题,所以必须要在平时的项目中不断地使用它们并解决真实的问题,因此学习编程的重点不少学而是用
- 面向对象设计规范原则是为了高效协作而诞生的
SOLID设计原则
SOLID设计原则是五个面向对象基本原则,它们为:
- S:Single Responsibility Principle 单一职责原则
- O:Open closed Principle 开闭原则
- L:Liskov Substitution Principle 里式替换原则
- I:Interface Segregation Principle 接口隔离原则
- D:Dependence Inversion Principle 依赖反转原则
抽象类与开闭原则概念
基本定义
- 抽象类(abstract class):是函数成员没有被完全实现的类
- 抽象类必须由abstract修饰
- 抽象类不能被实例化,只能作为基类或多态使用
- 被abstract修饰的函数成员不能为private
- 具体类(concrete class):是函数成员都被实现了的类
- 抽象方法也被称为纯虚方法
- “开放/关闭原则”:
- 若不是为了修BUG或增加新功能,尽量不要修改一个类的代码,特别是类中函数成员的代码
- 我们应该封装那些不变的、稳定的、固定的和确定的成员
- 把不确定的、有可能改变的成员声明为抽象成员,并留给子类去实现
为做基类而生的”抽象类”与”开放/关闭原则”演变过程示例:
示例1:普通类的定义
- 现在有两个类Car和Truck,它们都有Run和Stop方法,如下: ```csharp using System;
namespace Example027 { class Program { static void Main(string[] args) { } }
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!");
}
}
}
<a name="LZkn3"></a>
### 示例2:使用继承
- 由于示例1的两个类中都有相同的Stop方法,这违反了不能copy paste的设计原则,用继承的方式对代码进行改写,如下:
```csharp
using System;
namespace Example027
{
class Program
{
static void Main(string[] args)
{
Vehicle v = new Car();
v.Stop();//无法调用Run方法
}
}
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!");
}
}
}
示例3:基类声明带参数方法
- 此时发现,在主函数中,使用Vehicle v = new Car();多态后,无法调用Run方法。使用第一种办法是在Vehicle类中定义一个带参数的Run方法,如下: ```csharp using System;
namespace Example027 { class Program { static void Main(string[] args) { Vehicle v = new Car(); v.Run(“car”);//输出:Car is running! } }
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!");
}
}
}
class Car : Vehicle
{
public void Run()
{
Console.WriteLine("Car is running!");
}
}
class Truck : Vehicle
{
public void Run()
{
Console.WriteLine("Truck is running!");
}
}
}
<a name="B1bjX"></a>
### 示例4:使用重写
- 以上示例3中,已经违反了开闭原则,因为如果继续添加新子类,则需要在Vehicle父类中再次修改Run方法,用以添加子类的Run方法。使用第二种方法是运用重写,如下:
```csharp
using System;
namespace Example027
{
class Program
{
static void Main(string[] args)
{
Vehicle v = new Car();
v.Run();//输出:Car is running!
}
}
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!");
}
}
}
示例5:使用抽象方法
- 使用多态时,Vehicle类中的Run方法永远不会被调用到,既然无法被调用,就把Run变成纯虚方法。使用第三种方法是运用纯虚方法(抽象方法),如下: ```csharp using System;
namespace Example027 { class Program { static void Main(string[] args) { Vehicle v = new Car(); v.Run();//输出:Car is running! } }
//Vehicle类变为抽象类,只能作为基类,不能被实例化
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!");
}
}
}
<a name="LxyrP"></a>
### 示例6:基类所有方法都为纯虚方法
- 假设有一个类里面的方法都是纯虚方法,如下:
```csharp
using System;
namespace Example027
{
class Program
{
static void Main(string[] args)
{
Vehicle v = new Car();
v.Run();
}
}
//VehicleBase类中所有方法都为抽象方法
abstract class VehicleBase
{
abstract public void Stop();
abstract public void Run();
abstract public void Fill();
}
//Vehicle类继承VehicleBase类,Run方法没被实现,所以Vehicle也为抽象类
abstract class Vehicle : VehicleBase
{
public override void Stop()
{
Console.WriteLine("Stopped!");
}
public override void Fill()
{
Console.WriteLine("Pay and fill!");
}
}
//Car类所有方法都被实现,为具体类
class Car : Vehicle
{
public override void Run()
{
Console.WriteLine("Car is running!");
}
}
//Truck类所有方法都被实现,为具体类
class Truck : Vehicle
{
public override void Run()
{
Console.WriteLine("Truck is running!");
}
}
}
示例7:使用接口
- 以上示例6中VehicleBase抽象类的所有方法都是抽象方法,这种类在C#中称为接口,接口中的方法abstract和public修饰符都必须省略,如下: ```csharp using System;
namespace Example027 { class Program { static void Main(string[] args) { Vehicle v = new Car(); v.Run(); } }
interface IVehicle
{
void Stop();
void Run();
void Fill();
}
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!");
}
}
} ```
什么是接口和抽象类
- 接口和抽象类都是”软件工程产物”
- 具体类 -> 抽象类 -> 接口:越来越抽象,内部实现的东西越来越少
- 抽象类是未完全实现逻辑的类(可以有字段和非public成员,它们代表了”具体逻辑”)
- 抽象类为复用而生:专门作为基类来使用,也具有解耦功能
- 封装确定的,开放不确定的,推迟到合适的子类中去实现
- 接口是完全未实现逻辑的”类”(“纯虚类”;只有函数成员,成员全部为public)
- 接口为解耦而生:”高内聚,低耦合”,方便单元测试
- 接口是一个”协约”,早已为工业生产所熟知(有分工必有协作,有协作必有协约)
- 它们都不能实例化,只能用来声明变量、引用具体类的实例