1 什么是抽象

1.1 关于多态

在面向对象编程中,多态是指一类事物有多种形态。比如A类,A-1和A-2都属于动物类,它们都有A类中公共的方法

由于多态的存在,每个子类都可以覆写父类的方法,例如下面的代码中子类1和2都覆写了父类中的某个方法:

在这里,假如我们要设计一款游戏名叫 Apax 的枪械游戏。在这款游戏中,我们的武器就是枪械,所以我们来为我们的游戏设计一个 Weapon 类,它代表所有枪械的父类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 父类-武器
class Weapon {
// 具有的方法
// 子弹类型
public void WeaponAmmo() { … }
// 击中要害转化倍率
public void WeaponForce() { … }
// ...
}
// 子类1-轻型弹药武器
class LightAmmo extends Weapon {
@Override
public void WeaponAmmo() { … }
}
// 子类2-霰弹类武器
class ShotgunAmmo extends Weapon {
@Override
public void WeaponAmmo() { … }
}

1.2 关于抽象

可以看到, WeaponAmmo 方法在子类中是覆写的父类的方法,并且父类也确实具有 WeaponAmmo 方法的实现。但是如果父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,那么,可以把父类的方法声明为抽象方法:

1
2
3
4
class Weapon {
// 此处并未赋予WeaponAmmo方法任何实现,仅仅只是定义了WeaponAmmo方法的签名
public abstract void WeaponAmmo();
}

把一个方法声明为 abstract ,表示它是一个抽象方法,本身没有实现任何方法语句。因为这个抽象方法本身是无法执行的,所以, Weapon 类也无法被实例化。编译器会告诉我们,无法编译 Weapon 类,因为它包含抽象方法。必须把 Weapon 类本身也声明为 abstract ,才能正确编译它:

1
2
3
abstract class Weapon {
public abstract void WeaponAmmo();
}

如果一个 class 定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用 abstract 修饰。

因为无法执行抽象方法,因此这个类也必须申明为抽象类(abstract class)。

使用 abstract 修饰的类就是抽象类。我们无法实例化一个抽象类:

1
Weapon w = new Weapon(); // 编译错误

正确的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// 定义抽象类Weapon
abstract class Weapon {
// 抽象方法,需要具体子类实现
public abstract void WeaponAmmo();
}

// 定义LightAmmo类,继承自Weapon类并实现其抽象方法
class LightAmmo extends Weapon {
@Override
public void WeaponAmmo() {
System.out.println("使用轻型弹药");
}
}

// 定义主类Main,包含程序入口main方法
public class Main {
public static void main(String[] args) {
// 创建LightAmmo的实例并调用其方法
LightAmmo w = new LightAmmo();
w.WeaponAmmo();
}
}

当我们定义了抽象类 Weapon ,以及具体的 LightAmmoShotgunAmmo 子类的时候,我们可以通过抽象类 Weapon 类型去引用具体的子类的实例:

1
2
Weapon R301 = new LightAmmo();
Weapon Peacekeeper = new ShotgunAmmo();

这种尽量引用高层类型,避免引用实际子类型的方式,称之为面向抽象编程。

面向抽象编程的本质就是:

  • 上层代码只定义规范;

  • 不需要子类就可以实现业务逻辑;

  • 具体的业务逻辑由不同的子类实现,调用者并不关心。

2 进一步理解抽象

将复杂的概念进行封装与简化

抽象减少了信息的负载,否则需要全面详细地记录与处理事物的所有细节(熵减)。抽象的目标是简化看待事物的方式,允许忽略一些不重要的东西。

  • 编程的时候,并不需要直接操作比特流,这一切都是编译器提供的功能。

    • 可以说编译器在编译的时候做了一层抽象。
  • 抽象就是隐藏细节,只暴露接口。在使用计算机的usb等接口的时候,我们只需要做连接操作,不需要关心它的具体实现。

    • 可以说计算机接口在运行的时候做了一层抽象。

在写代码的时候,我们可以在一开始就定义好接口,然后具体实现的时候再去实现。这样可以提高代码可读性且减少代码量与复杂度。