
本文探讨了在java中,当一个类需要操作另一个类的现有对象时,如何避免不必要的对象重复创建。通过详细的代码示例,文章阐述了将现有对象作为方法参数传递的有效策略,从而实现类之间的松耦合协作,提升代码的灵活性、可维护性和测试性,并避免了静态方法或类合并可能带来的设计缺陷。
引言
在面向对象编程中,不同的类之间经常需要协同工作以完成复杂的任务。一个常见的场景是,我们可能在一个类(例如 Main 类)中创建了一个对象(例如 Car 对象),然后希望另一个类(例如 FuelConsumptionMonitor 类)能够对这个已存在的 Car 对象执行操作,例如监控其燃油消耗。初学者有时会遇到一个困惑:如何在 FuelConsumptionMonitor 类中访问 Car 对象的方法,而又不想在 FuelConsumptionMonitor 内部再次创建一个全新的 Car 对象?
本文将深入探讨这一问题,并提供一种优雅且符合面向对象设计原则的解决方案,即通过方法参数传递对象实例。
问题剖析:避免不必要的对象实例化
假设我们有一个 Car 类,它包含了引擎状态、燃油量等属性以及启动、停止、消耗燃油等方法。我们希望创建一个 FuelConsumptionMonitor 类,其职责是根据 Car 的当前状态(例如引擎是否开启)来计算并模拟燃油消耗。
如果我们在 FuelConsumptionMonitor 类的方法中直接使用 new Car() 来创建一个新的 Car 对象,那么这个 FuelConsumptionMonitor 操作的将是一个与 Main 类中创建的 Car 对象完全独立的、全新的实例。这显然不符合我们的初衷,因为我们想要监控的是 Main 中那个特定的 Car 实例的燃油消耗,而不是另一个无关的 Car。
立即学习“Java免费学习笔记(深入)”;
虽然将方法声明为 static 或将所有逻辑合并到 Car 类中可以在某种程度上“解决”表面问题,但这通常不是最佳实践:
- 静态方法 (static):静态方法属于类本身,而非类的某个特定实例。如果燃油消耗逻辑需要访问特定 Car 对象的实例变量(如 fuelLevel 或 engineOn),那么静态方法将无法直接实现,或者需要将这些实例变量也声明为静态,这会破坏对象的封装性,并导致所有 Car 实例共享相同的状态,这在大多数情况下是不合理的。
- 合并类:将 FuelConsumptionMonitor 的逻辑直接放入 Car 类,虽然可以避免对象传递,但会增加 Car 类的职责,使其承担了“汽车自身行为”和“燃油监控逻辑”两部分职责,这违反了单一职责原则 (Single Responsibility Principle, SRP)。一个设计良好的类应该只有一个引起它变化的原因。
因此,我们需要一种方式,让 FuelConsumptionMonitor 能够“看到”并操作 Main 中已创建的 Car 对象,而不是自己创建一个新的。
解决方案:通过方法参数传递对象
最直接且符合面向对象原则的解决方案是:将需要操作的 Car 对象作为参数传递给 FuelConsumptionMonitor 类的方法。这种方式也被称为依赖注入 (Dependency Injection) 的一种简单形式。
当 FuelConsumptionMonitor 的某个方法需要对一个 Car 对象进行操作时,我们不让 FuelConsumptionMonitor 自己去创建 Car 对象,而是由外部(例如 Main 方法)创建好 Car 对象,然后将其“注入”或“传递”给 FuelConsumptionMonitor 的方法。
代码示例
我们将通过三个类来演示这个解决方案:
- Car 类:代表汽车,包含基本属性和行为。
- FuelConsumptionMonitor 类:负责监控和计算燃油消耗。
- Main 类:程序的入口,创建 Car 和 FuelConsumptionMonitor 对象,并协调它们之间的交互。
1. Car.java
public class Car {
private double fuelLevel;
private boolean engineOn;
private String model;
public Car(String model, double initialFuel) {
this.model = model;
this.fuelLevel = initialFuel;
this.engineOn = false; // 初始引擎关闭
System.out.println(model + " 汽车已创建,初始油量: " + initialFuel + " 升。");
}
public void startEngine() {
if (!engineOn) {
engineOn = true;
System.out.println(model + " 引擎启动。");
} else {
System.out.println(model + " 引擎已在运行。");
}
}
public void stopEngine() {
if (engineOn) {
engineOn = false;
System.out.println(model + " 引擎关闭。");
} else {
System.out.println(model + " 引擎已关闭。");
}
}
public boolean isEngineOn() {
return engineOn;
}
public void consumeFuel(double amount) {
if (fuelLevel >= amount) {
fuelLevel -= amount;
System.out.printf("%s 消耗 %.2f 升燃油,剩余油量: %.2f 升。\n", model, amount, fuelLevel);
} else {
System.out.printf("%s 燃油不足,无法消耗 %.2f 升。当前油量: %.2f 升。\n", model, amount, fuelLevel);
stopEngine(); // 燃油不足时自动关闭引擎
}
}
public double getFuelLevel() {
return fuelLevel;
}
public String getModel() {
return model;
}
}2. FuelConsumptionMonitor.java
public class FuelConsumptionMonitor {
/**
* 根据汽车状态计算并消耗燃油。
* @param car 需要操作的 Car 对象
* @param durationMinutes 持续时间(分钟)
*/
public void monitorAndConsume(Car car, int durationMinutes) {
// 参数校验,确保 Car 对象不为空
if (car == null) {
System.out.println("错误:Car 对象不能为 null,无法监控燃油消耗。");
return;
}
System.out.printf("\n--- 监控 %s 的燃油消耗 (%d 分钟) ---\n", car.getModel(), durationMinutes);
double consumptionRatePerMinute = 0; // 每分钟消耗量
if (car.isEngineOn()) {
consumptionRatePerMinute = 0.8; // 引擎开启,静止状态下每分钟消耗 0.8 升
System.out.println(car.getModel() + " 引擎已启动,按静止状态消耗燃油。");
// 假设这里可以根据其他状态(如行驶)调整消耗率
// if (car.isMoving()) { consumptionRatePerMinute = 6.0; } // 示例:如果Car有isMoving方法
} else {
System.out.println(car.getModel() + " 引擎未启动,不消耗燃油。");
return; // 引擎未启动则不消耗
}
double totalConsumption = consumptionRatePerMinute * durationMinutes;
car.consumeFuel(totalConsumption); // 调用传入 Car 对象的 consumeFuel 方法
System.out.printf("--- 监控结束,%s 剩余油量: %.2f 升 ---\n", car.getModel(), car.getFuelLevel());
}
/**
* 仅计算预期燃油消耗量,不实际消耗。
* @param car 需要查询的 Car 对象
* @param durationMinutes 持续时间(分钟)
* @return 预期的燃油消耗量
*/
public double calculateExpectedConsumption(Car car, int durationMinutes) {
if (car == null || !car.isEngineOn()) {
return 0;
}
double consumptionRatePerMinute = 0.8;
// if (car.isMoving()) { consumptionRatePerMinute = 6.0; }
return consumptionRatePerMinute * durationMinutes;
}
}3. Main.java
public class Main {
public static void main(String[] args) {
// 1. 在主方法中创建 Car 对象实例
Car myCar = new Car("Tesla Model S", 50.0); // 创建一个名为 "Tesla Model S" 的汽车,初始油量50升
// 2. 创建 FuelConsumptionMonitor 对象实例
FuelConsumptionMonitor monitor = new FuelConsumptionMonitor();
// 3. 调用 Car 对象的方法,改变其状态
myCar.startEngine(); // 启动汽车引擎
// 4. 将 myCar 对象作为参数传递给 FuelConsumptionMonitor 的方法
// 这样,FuelConsumptionMonitor 操作的就是 myCar 这个具体的、已存在的实例
System.out.println("\n--- 第一次燃油消耗监控 ---");
monitor.monitorAndConsume(myCar, 10); // 监控10分钟的燃油消耗
// 模拟汽车行驶一段时间后的状态变化
System.out.println("\n--- 模拟汽车状态变化后再次监控 ---");
myCar.stopEngine(); // 先关闭引擎
myCar.startEngine(); // 再次启动引擎
monitor.monitorAndConsume(myCar, 5); // 监控5分钟
// 尝试在引擎关闭时进行监控
System.out.println("\n--- 尝试在引擎关闭时监控 ---");
myCar.stopEngine(); // 关闭引擎
monitor.monitorAndConsume(myCar, 3); // 此时不应消耗燃油
// 尝试油量不足时的消耗
System.out.println("\n--- 尝试油量不足时的消耗 ---");
myCar.startEngine();
monitor.monitorAndConsume(myCar, 100); // 尝试消耗大量燃油,导致油量不足
}
}运行结果示例
Tesla Model S 汽车已创建,初始油量: 50.0 升。 Tesla Model S 引擎启动。 --- 第一次燃油消耗监控 --- Tesla Model S 引擎已启动,按静止状态消耗燃油。 Tesla Model S 消耗 8.00 升燃油,剩余油量: 42.00 升。 --- 监控结束,Tesla Model S 剩余油量: 42.00 升 --- --- 模拟汽车状态变化后再次监控 --- Tesla Model S 引擎关闭。 Tesla Model S 引擎启动。 Tesla Model S 引擎已启动,按静止状态消耗燃油。 Tesla Model S 消耗 4.00 升燃油,剩余油量: 38.00 升。 --- 监控结束,Tesla Model S 剩余油量: 38.00 升 --- --- 尝试在引擎关闭时监控 --- Tesla Model S 引擎关闭。 Tesla Model S 引擎未启动,不消耗燃油。 --- 监控结束,Tesla Model S 剩余油量: 38.00 升 --- --- 尝试油量不足时的消耗 --- Tesla Model S 引擎启动。 Tesla Model S 引擎已启动,按静止状态消耗燃油。 Tesla Model S 燃油不足,无法消耗 80.00 升。当前油量: 38.00 升。 Tesla Model S 引擎关闭。 --- 监控结束,Tesla Model S 剩余油量: 38.00 升 ---
从输出中可以看出,FuelConsumptionMonitor 成功地操作了 Main 方法中创建的 myCar 对象,并且其燃油量和引擎状态都得到了正确的更新。
优势分析
通过方法参数传递对象,这种模式带来了多方面的好处:
- 松耦合 (Loose Coupling):FuelConsumptionMonitor 类不再需要知道如何创建 Car 对象,它只需要知道如何与一个已存在的 Car 对象进行交互。这使得两个类之间的依赖关系变得松散,降低了修改其中一个类时对另一个类造成影响的可能性。
- 高灵活性 (High Flexibility):FuelConsumptionMonitor 可以与任何 Car 实例一起工作。你可以在 Main 方法中创建多个 Car 对象,并将它们分别传递给 monitorAndConsume 方法,FuelConsumptionMonitor 都能正确处理。
- 易于测试 (Easier Testing):在单元测试中,我们可以轻松地创建 Car 对象的模拟 (mock) 或存根 (stub) 版本,并将其传递给 FuelConsumptionMonitor 进行测试,而无需担心 Car 类的复杂实现细节。
- 符合单一职责原则 (Single Responsibility Principle, SRP):Car 类专注于管理汽车自身的属性和行为,而 FuelConsumptionMonitor 类则专注于燃油消耗的监控逻辑。每个类都只负责一项职责,使得代码更清晰、更易于维护。
- 避免状态混淆:确保 FuelConsumptionMonitor 操作的是我们期望的那个特定 Car 实例,而不是一个全新的、拥有默认状态的 Car。
进阶考虑:构造器注入
除了通过方法参数传递对象外,如果 FuelConsumptionMonitor 类的整个生命周期










