接口与抽象类的重要性

  • 描述接口和抽象类的区别和用法不同是面试必考的一些内容
  • 在日常工作中,若不能熟练驾驭接口和抽象类的使用,则写出的代码很可能比较难测试,比较难维护
  • 接口和抽象类是面向对象设计中最关键、最精妙的部分,没有它们就没有现代面向对象设计和现代软件工程,是软件工业设计的两块基石,它们也是所有高阶面向对象设计的起点

关于设计模式

  • 学习设计模式需要两个前提:
    • 必须已透彻理解何为接口和抽象类,并在实际项目中熟练使用
    • 必须已深入了解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) { } }

  1. class Car
  2. {
  3. public void Run()
  4. {
  5. Console.WriteLine("Car is running!");
  6. }
  7. public void Stop()
  8. {
  9. Console.WriteLine("Stopped!");
  10. }
  11. }
  12. class Truck
  13. {
  14. public void Run()
  15. {
  16. Console.WriteLine("Truck is running!");
  17. }
  18. public void Stop()
  19. {
  20. Console.WriteLine("Stopped!");
  21. }
  22. }

}


<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)
  • 接口为解耦而生:”高内聚,低耦合”,方便单元测试
  • 接口是一个”协约”,早已为工业生产所熟知(有分工必有协作,有协作必有协约)
  • 它们都不能实例化,只能用来声明变量、引用具体类的实例