C#学习笔记(二十二)抽象类与开闭原则:抽象类和接口怎么来的?

 接口抽象类是面向对象设计中最精妙最关键的部分。

学编程的重点:不是学,而是用。


抽象类:

函数成员没有被完全实现的类。(没有函数体。)

abstract class Student
{
    //只有返回值、函数名和参数列表,没有方法体。完全没有被实现的方法,也就是抽象方法。
    public abstract void Study();
    // 一个类一旦有了抽象方法或者其他抽象成员,这个类就是抽象类。
}

abstract成员不能是private。

因为抽象类的子类去实现抽象类的方法,如果子类都没法访问,就更没法实现了,所以abstract成员不能是private。

编译器不允许实例化一个抽象类。

抽象类核心概念:因为抽象类含有未被实现的函数成员。

如果不能实例化这个抽象类,那么抽象类的功能:抽象类是专门为做基类而生的。

  1. 作为基类派生其他子类。在派生类中把没有实现的函数成员,通过override重写实现了。
  2. 抽象类作为基类,可以用来声明变量。用基类类型的变量去引用子类类型的实例。

抽象类唯一能做的事是专门为做基类而生的。


开闭原则:

我们应该封装一些固定,不变的,稳定的、确定的成员;把不确定的,有可能改变的成员抽象为抽象成员、抽象类,留给子类去实现。

抽象类和开闭原则天生就是一对。


 演化过程:抽象类和接口怎么来的?

 第一步:初始化与复制

我买了一辆车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)的实例。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值