| Lesson 7 | Factory Method: consequences |
| Objective | Write a class using the Factory Method Pattern |
In the previous lessons, you built the Vehicle abstraction and then created concrete subclasses
(Car, Bus, Bicycle, Pedestrian). This lesson completes the Factory Method workflow:
you will write a class that creates those subclasses through an interface, so client code depends on
Vehicle (the abstraction) rather than concrete class names.
Without a factory, client code typically contains explicit construction logic:
// Client code WITHOUT a factory (tight coupling)
Vehicle v = new Car(); // client is now coupled to "Car"
With a Factory Method, client code requests a Vehicle from a creator, and the creator decides which concrete
subclass to instantiate:
// Client code WITH a factory (loose coupling)
Vehicle v = factory.createVehicle();
That single change has measurable consequences—both good and bad.
Vehicle, not Car/Bus/Bicycle.
This reduces compile-time coupling and makes the client easier to evolve.
Vehicle subclass (for example Motorcycle) can often be done by extending
the factory logic rather than changing every call site.
Vehicle requires creating a matching companion object (for example, a Driver),
the factory can guarantee consistent pairing:
one vehicle → one correct driver subtype.
new at the call site.
The key is to apply Factory Method when it buys you real flexibility: multiple concrete subclasses, runtime selection, or a desire to hide construction details from clients.
If every client has to decide which subclass to instantiate, you usually end up with repeated conditional logic scattered throughout the codebase:
// Example problem: repeated selection logic in every client
Vehicle v;
if (config.equals("car")) v = new Car();
else if (config.equals("bus")) v = new Bus();
else v = new Bicycle();
That design has predictable weaknesses:
Factory Method moves that responsibility to a creator class that exists specifically to manage creation policy.
Because your module already defines multiple concrete Vehicle subclasses, the simplest next step is a
parameterized factory. This avoids creating a separate creator class per concrete product while still returning
the abstract type Vehicle.
public class VehicleFactory {
public static final String CAR = "car";
public static final String BUS = "bus";
public static final String BICYCLE = "bicycle";
public static final String PEDESTRIAN = "pedestrian";
public Vehicle createVehicle(String type) {
if (type == null) {
throw new IllegalArgumentException("type must not be null");
}
String t = type.trim().toLowerCase();
if (CAR.equals(t)) return new Car();
if (BUS.equals(t)) return new Bus();
if (BICYCLE.equals(t)) return new Bicycle();
if (PEDESTRIAN.equals(t)) return new Pedestrian();
throw new IllegalArgumentException("Unknown vehicle type: " + type);
}
}
This class demonstrates the lesson objective directly:
it is a dedicated creation component that returns Vehicle while selecting the correct concrete subclass internally.
In modern systems, you may see this same idea implemented with dependency injection containers, registries, or service loaders. Even then, the Factory Method principle remains unchanged: centralize creation policy and return abstractions.