Java 11 Cookbook
上QQ阅读APP看书,第一时间看更新

Aggregation makes the design more extensible

In the preceding example, the speed model was implemented in the getSpeedMph(double timeSec) method of the Vehicle class. If we need to use a different speed model (which includes more input parameters and is more tuned to certain driving conditions, for example), we would need to change the Vehicle class or create a new subclass to override the method. In the case where we need to experiment with dozens or even hundreds of different models, this approach becomes untenable.

Also, in real life, modeling based on machine learning and other advanced techniques become so involved and specialized, that it is quite common that the modeling of car acceleration is done by a different team, not the team that assembles the vehicle model.

To avoid the proliferation of subclasses and code-merge conflicts between vehicle builders and speed-model developers, we can create a more extensible design using aggregation. 

Aggregation is an OOD principle for implementing the necessary functionality using the behavior of classes that are not part of the inheritance hierarchy. That behavior can exist independent of the aggregated functionality.

We can encapsulate the speed calculations inside the SpeedModel class in the getSpeedMph(double timeSec) method: 

public class SpeedModel{
private Properties conditions;
public SpeedModel(Properties drivingConditions){
this.drivingConditions = drivingConditions;
}
public double getSpeedMph(double timeSec, int weightPounds,
int horsePower){
String road =
drivingConditions.getProperty("roadCondition","Dry");
String tire =
drivingConditions.getProperty("tireCondition","New");
double v = 2.0 * horsePower * 746 * timeSec *
32.17 / weightPounds;
return Math.round(Math.sqrt(v)*0.68)-road.equals("Dry")? 2 : 5)
-(tire.equals("New")? 0 : 5);
}
}

An object of this class can be created and then set as the value of the Vehicle class field: 

public class Vehicle {
private SpeedModel speedModel;
private int weightPounds, horsePower;
public Vehicle(int weightPounds, int horsePower) {
this.weightPounds = weightPounds;
this.horsePower = horsePower;
}
public void setSpeedModel(SpeedModel speedModel){
this.speedModel = speedModel;
}
public double getSpeedMph(double timeSec){
return this.speedModel.getSpeedMph(timeSec,
this.weightPounds, this.horsePower);
}
}

The test class changes as follows:

public static void main(String... arg) {
double timeSec = 10.0;
int horsePower = 246;
int vehicleWeight = 4000;
Properties drivingConditions = new Properties();
drivingConditions.put("roadCondition", "Wet");
drivingConditions.put("tireCondition", "New");
SpeedModel speedModel = new SpeedModel(drivingConditions);
Car car = new Car(4, vehicleWeight, horsePower);
car.setSpeedModel(speedModel);
System.out.println("Car speed (" + timeSec + " sec) = " +
car.getSpeedMph(timeSec) + " mph");
}

The result of the preceding code is as follows:

We isolated the speed-calculating functionality in a separate class and can now modify or extend it without changing any class of the Vehicle inheritance hierarchy. This is how the aggregation design principle allows you to change the behavior without changing the implementation.

In the next recipe, we will show you how the OOP concept of interface unlocks more power of aggregation and polymorphism, making the design simpler and even more expressive.