面向对象编程学习笔记

发布时间 2023-12-17 10:47:24作者: XYukari

一、类与实例

什么是类?

  1. 类是抽象数据结构;
  2. 类是用户自定义的数据类型;
  3. 类是对客观世界事物种类的抽象

类与实例

类是设计房屋的蓝图,实例是按照蓝图建造出来的具体的房屋,实例化就是按照蓝图进行建造。具体实现上,用类类型的变量引用创建出的实例,进行各种操作。

二、继承

为了从逻辑上表达类之间的包含与被包含关系(“车”之于“轿车”“卡车”,“人”之于“工人”“农民”),我们引入继承的概念——当令类 A 继承类 B 的时候,A 获得了 B 中访问级别为 public 和 protected 的属性和方法。

继承清晰的表达了事物之间的逻辑关系。除此之外,继承的优点还有:

  1. 避免代码重复,不需要给轿车、卡车分别写一个 Run() 方法,而是创建一个有 Run() 方法的“车辆”类,让各种车进行继承;
  2. 添加、删除共有方法时,直接在父类中修改,而不需要对每个类一一修改,便于维护。

有的语言允许多重继承,即一个子类对应多个父类。

三、重写与多态

虽然轿车、卡车都能跑,但是跑的方式并不完全相同,从父类“车辆”继承来的 Run() 方法却只有一个,所以我们引入重写的概念,即在子类中编写父类的同名方法,实现自己独特的功能。

这看起来与继承是矛盾的。既然和父类的方法不一样,为什么还要继承呢?为什么不直接在子类中写自己的方法呢?我们先考虑另一个问题。

父类类型的变量也可以引用子类的实例,但是这样的变量无法访问父类没有而子类独有的属性和方法。因此,我们在父类中声明方法,在子类中重写,让父类变量也可以看到这些方法,让父类变量具有多态性(可以引用不同的子类实例)。如果直接在子类中写自己的方法,多态就无法实现。所以重写是多态的基础。

为什么需要多态?

如果用 Car 类型变量引用 Car 实例,用 Truck 类型变量引用 Truck 实例,不是更直接吗?

一个直接的意义是方便传参:比如大乔开大,并不知道传过来的是哪个英雄,干脆直接把参数类型设置为所有英雄的父类。这实际上是软件工程追求“统一性”的体现。

另一方面,多态可以降低耦合度;在类中声明其他类的变量,会造成两个类的紧耦合,这是十分危险的(一旦出错会引发连锁反应,而软件追求的是部分出错、其余部分仍然能使用),如果声明了一堆子类变量,会造成大量的紧耦合,而用父类变量则相对安全。

四、抽象类和接口

抽象方法

如果一个方法自身不会被调用,存在的意义只是被子类重写,用于体现多态,显然我们根本不需要编写它的方法体。我们用 abstract 关键字将其声明为抽象方法。包含抽象方法的类是抽象类。

抽象类不能被实例化,否则有可能会调用到其中的抽象方法,引发错误(编译器层面的限制,为了保证安全)。

在抽象类的子类中,所有的抽象方法要么被实现(重写),要么仍然用 abstract 关键字声明为抽象方法,继续下传。如果存在后者这种情况,这个子类也必须是抽象类。

接口

接口是抽象类演变而来的。当抽象类中只有抽象方法时,这个类就是一个接口,改用 interface 关键字而非 class 关键字声明。

类可以实现接口,实际上也就是继承一个只有抽象方法的抽象类。稍有不同的是,一个类只能继承一个类,但可以实现多个接口。

五、SOLID 原则

开闭原则

除非需要修 bug,不应该修改类体,而应该创建新的类,通过实现接口的方式达到想要的功能,避免引发潜在的问题。

接口隔离

不要依赖用不到的接口——把胖接口拆分成更小、更具体的接口,类实现最小的接口。

依赖反转

把功能抽象为接口,让不同的类去实现接口,避免紧耦合。