c#入门详解:抽象类、开闭原则和接口入门

面试经典提问:描述接口和抽象类的区别,以及它们用法的不同

引言

软件也是工业的分支,设计严谨的软件必须经得起测试。

软件能不能测试、测试出问题后好不好修复、软件整体运行状态好不好监控,都依赖于对接口和抽象类的使用。

接口和抽象类是现代面向对象的基石,也是高阶面向对象程序设计的起点。

学习设计模式的前提:

  1. 透彻理解并熟练使用接口和抽象类
  2. 深入理解 SOLID 设计原则,并在日常工作中自觉得使用它们

SOLID原则(五原则)

SOLID 原则是面向对象 class 设计的五条原则,是设计 class 结构时应该遵守的准则和最佳实践。

首字母指代概念
S单一功能原则对象应该仅具有一种单一功能。
O开闭原则软件体应该是对于扩展开放的,但是对于修改封闭的。
L里氏替换原则程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换。
参考 契约式设计
I接口隔离原则多个特定客户端接口要好于一个宽泛用途的接口。
D依赖反转原则一个方法应该遵从“依赖于抽象而不是一个实例”。依赖注入是该原则的一种实现方式。
  • SRP:Single Responsibility Principle
  • OCP:Open Closed Principle
  • LSP:Liskov Substitution Principle
  • ISP:InterfaceSegregation Principle
  • DIP:Dependency Inversion Principle

抽象类和开闭原则有密切的联系。
设计原则的重要性和它在敏捷开发中扮演的重要角色:

  1. 之前学了类的封装与继承,理论上爱怎么用怎么用,但要写出高质量、工程化的代码,就必须遵循一些规则
  2. 写代码就必须和人合作,即使是你一个人写的独立软件,未来的你也会和现在的你合作
  3. 这些规则就如同交通规则,是为了高效协作而诞生的
    1. 硬性规定:例如变量名合法,语法合法
    2. 软性规则:编程规范

抽象类

  • 一个类里面一旦有了 abstract 成员,类就变成了抽象类,就必须标 abstract。
  • 抽象类内部至少有一个函数成员未完全实现。
  • abstract 成员即暂未实现的成员,因为它必须在子类中被实现,所以抽象类不能是 private 的。
abstract class Student{
    abstract public void Study();
}

抽象类的两个作用
  1. 作为基类,在派生类里面实现基类中的 abstract 成员
  2. 声明基类(抽象类)类型变量去引用子类(已实现基类中的 abstract 成员)类型的实例,这又称为多态

抽象方法的实现,看起来和 override 重写 virtual 方法有些类似,所以抽象方法在某些编程语言(如 C++)中又被称为“纯虚方法”。
virtual(虚方法)还是有方法体的,只不过是等着被子类重写,abstract(纯虚方法)却连方法体都没有。
PS:我们之前学的非抽象类又称为 Concrete Class。

开闭原则

  • 我们应该封装那些不变的、稳定的、固定的和确定的成员,而把那些不确定的、有可能改变的成员声明为抽象成员,并留给子类去实现。
  • 开放修复bug和添加新功能,关闭对类的修改。

如果不是为了修复bug和添加新功能,别轻易修改类的代码,特别是类当中函数的成员的代码

示例推导


代码必须至少敲一遍,写出来才能慢慢理解思路

以 Vehicle(交通工具)做为基类,让车子跑起来

虚方法版
 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");
        }
    }

使用虚方法遗留了一个问题:Vehicle 的 Run 方法的行为本身就很模糊,并且在实际应用中也根本不会被调用到,从测试角度来说,去测试一个永远用不到的代码是不合理的。

抽象类版

那就干脆 Run 方法里什么也不写,方法体 {} 也去掉,再加上 abstract 就变成了抽象方法 Run。因为有抽象成员,Vehicle 类也必须加上 abstract 变成抽象类。

   abstract class Vehicle // 抽象类
    {
        public  void Stop()
        {
            Console.WriteLine("Stopped");
        }

        public abstract void Run(); // 抽象方法     
    }

当 Vehicle 变成抽象类后,再添加新的继承于 Vehicle 的类格式也是一样,其余代码也不用变

 class Program
    {
        static void Main(string[] args)
        {
            Vehicle v = new RaceCar();
            v.Run();
            // Car is running...
        }
    }

  abstract class Vehicle // 抽象类
    {
        public void Stop()
        {
            Console.WriteLine("Stopped");
        }

        public abstract void Run(); // 抽象方法     
    }
    
    class RaceCar:Vehicle
    {
        public override void Run()
        {
            Console.WriteLine("RaceCar is running");
        }
    }
}

纯抽象类版(接口)

是否能更进一步,一个抽象类里的方法全是抽象方法?那就是纯抽象类

纯抽象版

创建纯抽象类 VehicleBase 类,将成员的实现往下推,到 Vehicle 实现部分成员,再向下推到子类将成员完全实现。

abstract class VehicleBase // 纯抽象类(纯虚类)
    {
        public abstract void Stop();
        public abstract void Run();
    }

abstract class Vehicle:VehicleBase // 抽象类
{
    public override void Stop()
    {
        Console.WriteLine("Stopped!");
    }

    public override void Fill()
    {
        Console.WriteLine("Pay and fill...");
    }
}

接口(interface)

在 C++ 中能看到这种纯虚类的写法,但在 C# 和 Java 中,纯虚类其实就是接口。
1. 因为 interface 要求其内部所有成员都是 public 的,所以就把 public 去掉了
2. 接口本身就包含了“是纯抽象类”的含义(所有成员一定是抽象的),所以 abstract 也去掉了
3.因为 abstract 关键字去掉了,所以抽象类的成员实现过程中的 override 关键字也去掉了
纯虚类演变成了接口,现在的代码架构就有点像平时工作中用的了。
接口(全抽象)- 抽象类(部分实现,至少一个抽象)- 子类(具体类,成员完全实现)

// 因为接口在 C# 中的命名约定以 I 开头,VehicleBase 改成 Ivehicle
interface IVehicle // 接口
    {
        void Stop();
        void Run();
    }


    abstract class Vehicle:IVehicle // 抽象类
    {
        public void Stop()
        {
            Console.WriteLine("Stopped");
        }

        public abstract void Run(); // 抽象方法     
    }

    class RaceCar : Vehicle
    {
        public override void Run()
        {
            Console.WriteLine("RaceCar is running");
        }
    }
}

总结

什么是接口和抽象类
●接口和抽象类都是“软件工程产物”
●具体类 -> 抽象类 -> 接口:越来越抽象,内部实现的东西越来越少
●抽象类是未完全实现逻辑的类(可以有字段和非 public 成员,它们代表了“具体逻辑”)
●抽象类为复用而生:专门作为基类来使用。也具有解耦功能
●封装确定的,开放不确定的(开闭原则),推迟到合适的子类中去实观
●接口是完全未实现逻辑的“类”(“纯虚类”;只有函数成员;成员全部 public)
●接口为解耦而生:“高内聚,低耦合”,方便单元测试
●接口是一个“协约”。早已为工业生产所熟知(有分工必有协作,有协作必有协约)
●它们都不能实例化。只能用来声明变量、引用具体类(concrete class)的实例

对于一个方法来说,方法体就是它的实现;对于数据成员,如字段,它就是对类存储数据的实现。

提问:

类、抽象类和接口之间的区别,如何理解它们?

1.抽象层次
接口是顶级抽象,抽象类是次级抽象,类是对世界的理解和具体实现。
2.关系继承
类,仅允许继承另一个类,该类可以是具体类,也可以是抽象类,但类,可以实现多个接口。
抽象类,在关系继承上,与具体类是一样的。
3.实现层次
类,是对世界的理解并使用具体逻辑进行实现。
抽象类,定义了世界的基本框架和结构要求,抽象类提供了所有子类的模板。
接口,是对世界的极致单一抽象。它是最小最细的抽象。
4.思想原则
三者在实际编码中,应严格遵循面向对象原则,即单一、开闭、替换、依赖倒转、接口隔离。通过这个思想原则,可以指导我们创建对于业务世界的编码理解。

5.区别
接口是顶级抽象,接口的抽象,是极为考验程序员水平能力的。接口抽象不单是针对业务抽象,还针对行为、设计、框架等多方面的思考,可以说你有多高的项目全局观,就可以设计出多抽象的接口。
抽象类是具体实现类的模板抽象,即使是单一业务的抽象类,因为考虑到实际实现的因素,可能都会有提供一些子类所需要的父类共性方法或属性信息。类,则是负责具体实现。

6.对外开放
在开放api方面,对于调用者来说,他并不需要知道你的api具体实现细节,仅需要知道有什么方法,实现什么功能,怎么使用即可。

记住:“所有类,都是某种意义的具体实现”,所以,我们应该尽可能隐藏类和抽象类,开放接口。

接口进阶详解:https://blog.csdn.net/m0_55074196/article/details/139356165

  • 14
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值