组合与继承
本课属于 Coddy Java 学习旅程的“面向对象编程”单元。第 47 课,共 87 课。
继承会创建“是一个”的关系:Dog是一个Animal。但如果一个类需要某种功能,而它并不是提供该功能的对象,该怎么办?这时就可以使用组合,建立“有一个”的关系。
使用组合时,一个类会将其他类的实例作为字段,而不是继承它们:
// 继承方式:Car 是 Engine(不合理)
class Car extends Engine { }
// 组合方式:Car 有一个 Engine(合理)
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.ignite();
}
}组合有几个优点。你可以在运行时更换组合对象,例如更换引擎。
你还可以组合多个类的行为,这是 Java 中的继承无法实现的。组合后的类只会公开它选择的方法,从而提供更好的封装性。
一个实用的准则:当存在真正的“是一种”关系,并且子类确实表示父类的一个专门版本时,使用继承。
当你需要使用另一个类的功能,但又不想成为该类型时,可以使用组合。许多经验丰富的开发者遵循这样一条原则:“优先使用组合,而不是继承”,因为这样可以让代码更灵活、更易于维护。
挑战
简单我们来使用组合构建一个计算机系统,演示“拥有”关系。让 Computer 继承自它的组件并不合理,因此你的计算机会包含正常运行所需的部件。
你会把代码分成 3 个文件:
Processor.java:创建一个表示 CPU 的类。Processor有两个私有字段:brand(String)和speedGHz(double)。添加一个构造函数来初始化这两个字段,并为每个字段添加 getter 方法。再添加一个process()方法,返回一个 String:[brand] processing at [speedGHz] GHzMemory.java:创建一个表示 RAM 的类。Memory有两个私有字段:type(String,例如 "DDR4" 或 "DDR5")和sizeGB(int)。添加构造函数、getter,以及一个load()方法,返回:Loading [sizeGB]GB [type] memoryComputer.java:组合的优势就在这里!你的Computer类应该将Processor和Memory作为私有字段。它不继承这两个类,而是包含它们。添加一个接受这两个组件的构造函数。再添加一个boot()方法,返回多行 String,通过使用这些组件显示计算机启动过程:另外,添加一个Booting computer... [processor.process() result] [memory.load() result] System ready!getSpecs()方法,返回:Specs: [processor brand] CPU, [memory sizeGB]GB [memory type]Main.java:让组合后的系统运行起来!你会收到 4 个输入:处理器品牌(String)、处理器速度(double)、内存类型(String)和内存大小(int)。使用这些值创建一个
Processor和一个Memory,然后将它们组合成一个Computer。打印调用boot()的结果,然后打印调用getSpecs()的结果。
你会依次收到 4 个输入:处理器品牌、处理器速度(GHz)、内存类型和内存大小(GB)。
注意,Computer 会把工作交给它的组件,而不是试图成为这些组件。这就是组合的优势!Computer 可以使用你提供的任何 Processor 或 Memory,让设计更灵活、更贴近实际。
亲手试试
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取输入
String processorBrand = scanner.nextLine();
double processorSpeed = Double.parseDouble(scanner.nextLine());
String memoryType = scanner.nextLine();
int memorySize = Integer.parseInt(scanner.nextLine());
// TODO: 用品牌和速度创建一个 Processor 对象
// TODO: 用类型和大小创建一个 Memory 对象
// TODO: 组合 Processor 和 Memory,创建一个 Computer 对象
// TODO: 打印 boot() 的结果
// TODO: 打印 getSpecs() 的结果
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Java 在线编译器