166. Closing the electrical panel before JDK 17
By its nature, an electrical panel is a closed unit of work. But, our code from the previous problem is far away from being a closed hierarchy. We can extend and implement almost any class/interface from inside or outside the hierarchy.Before JDK 17, closing a hierarchy of classes and interfaces can be done using several tools.
Applying the final modifier
For instance, we have the powerful final modifier. Once we declare a class as final it cannot be extended, so it is completely closed. Obviously, we cannot apply this technique consistently across a hierarchical model because it will lead to a non-hierarchical model.If we scan our electrical panel model then we can use the final modifier in several places. First, we eliminate interfaces (ElectricComponent, ElectricBreaker) since interfaces cannot be declared as final. Next, we can look at the ElectricCircuit class and its subclasses (ParallelCircuit, SeriesCircuit, and ShortCircuit). Obviously, since ElectricCircuit has subclasses it cannot be final. However, its subclasses are modeling notions that shouldn’t be extended so they can be final. This is our first step in obtaining a closed hierarchical model:
public final class ParallelCircuit extends ElectricCircuit {}
public final class SeriesCircuit extends ElectricCircuit {}
public final class ShortCircuit extends ElectricCircuit {}
Other classes that model well-defined notions that shouldn’t be extended are the classes that model capacitors, transistors, and resistors. So, the following classes can be final as well:
public final class CeramicCapacitor extends Capacitor {}
public final class ElectrolyticCapacitor extends Capacitor {}
public final class FieldEffectTransistor extends Transistor {}
public final class BipolarTransistor extends Transistor {}
public final class CarbonResistor extends Resistor {}
public final class MetalFilmResistor extends MetalResistor {}
public final class MetalOxideResistor extends MetalResistor {}
Finally, we have the ElectricPanel class. It doesn’t make sense to derive something from an electrical panel, so this class can be final as well:
public final class ElectricPanel implements ElectricBreaker {}
So far, we managed to close some parts of the hierarchy. There are no other places where the final modifier can help us, so we can go further and try another technique.
Defining package-private constructors
Next, we can use the hack of defining package-private constructors (a constructor with no visible modifier). The classes having package-private constructors can be instantiated and extended only inside that package – from a readability point of view this technique is far away from expressing its intentions. However, in complex designs, we can apply this technique sporadically since we cannot simply put everything in a single package. Nevertheless, it can be considered as a solution for increasing the hierarchical model closing level.For instance, we can focus on our abstract classes. They cannot be instantiated (being abstract) but they can be extended from anywhere. However, some of them should be extended only in the package where they are defined. The ElectricCircuit class is abstract and it should be extended only by ParallelCircuit, SeriesCircuit, and ShortCircuit. These subclasses live in the same package as ElectricCircuit, so it makes sense to use this hack of declaring a package-private constructor:
public abstract class ElectricCircuit
implements ElectricComponent {
ElectricCircuit() {}
…
}
Now, the ElectricCircuit class is closed to any extension attempt coming from outside of its package. Of course, it is still open to extension from inside of its package.