Again, I am all for template method pattern when it operates on fully initialized objects. But, by definition, Java constructors do not operate on such objects: they are exactly in the business of initializing them.
Imagine that your template method calls on an object that is still under construction - not something proponents of template method had in mind. But this is exactly what happens with Java constructors. Let's go straight to the example.
Suppose we have an abstract class Vehicle:
public abstract class Vehicle { private boolean registered = false; public Vehicle() { registered = registerWithDMV(getMileage()); } public abstract int getMileage(); private boolean registerWithDMV(int mileage) { return (registered = (mileage > 0) ? true : false); } public boolean isRegistered() { return registered; } }Every vehicle registers with DMV when created. Its constructor (using template method pattern) calls concrete method registerWithDMV and abstract method getMileage. So concrete sub-classes of the Vehicle must provide mileage:
public class Car extends Vehicle { private int mileage = 6; @Override public int getMileage() { return mileage; } }Our implementation for DMV registration is part of Vehicle for demonstration only. It is simple: give me non-zero mileage and you are good to go (drive that is). But alas, look at this test that promptly fails:
public class VehicleTests { @Test public void testNewCar() { Vehicle car = new Car(); assertTrue(car.isRegistered()); } }Unfortunately our template method (Vehicle default constructor) runs when Car object is not yet fully initialized: Java constructors run in order from higher in hierarchy (abstract) to lower (concrete) classes. That is why property mileage is still 0 and not 6 when constructor is run.
My first recommendation is not to use template method in constructors in Java at all. This is rather drastic but doable. Replace it with some init method that is called by concrete classes upon creation. If you don't like radical approaches then use lazy initialization and/or static variables in concrete classes:
public class Car extends Vehicle { private static int INITIAL_MILEAGE = 6; private int mileage; @Override public int getMileage() { if (mileage == 0) { mileage = INITIAL_MILEAGE; } return mileage; } }Test succeeds now. But lazy initialization may need more work: assuming mileage 0 is valid value you would end up introducing yet another property (flag) to indicate if mileage is initialized or not.
Again, my preference is avoiding this conflict all together by placing object initialization in the constructor of concrete class. Just think of Java constructors as non-polymorphic hierarchical artifacts.
No comments:
Post a Comment