I just met up with this scenario where I wanted to add some field in my gui panel which was shared across different use cases. But as always problem was twisted a bit. I only wanted to change panel for this use case. Base class code has some method like createFields() which used to add all fields of panel in defined layout.
Public class BasePanelClass()
{
private List components;
private List labels;
Protected void createFields()
{
// create component add it to the panel
addComponent(component1, label1);
.
.
addComponent(component2, label2);
.
.
.
}
/**
* This method adds component and label to some collection.
* Lets say draw() method will later use this collection to iterate on all components
* and put them configured layout on the panel to show
*/
Private void addComponent(component, label)
{
labels.add(label);
components.add(component);}
}
}
Now I can solve this thing by many different approaches.
i) From createFields() I can call some new protected method say addNewField(). This call will be made from point where this new field should be added. It will have empty default implementation in base class. There will be child class for this scenario say Child1PanelClass which will override this method to add required field.
ii) I can make use of flag to decide whether to or not to add required field. This even doesn't need subclassing.
Both of these approaches may serve the need and even second approach may seem like least work required and job done. Because when it comes to some very old applications where people already having nightmares maintaining them everyone wants to keep changes in code as minimum as possible.
However this is not good idea to do that. Adding addNewField() method to base class is totally irrelevant to other flows. What if some other flow later needed to add some other field in same panel. With these 2 approaches we will end up in touching panel base class code. Which may impact other scenarios and certainly will make base class messy.
So what we need to do is go back to basics of inheritance. Base class should represent things which are common to all childs. There should not be dependency downwards from base class to child class either by some empty protected method which very specific to some child class or by some conditional checks which needs to be maintained every time there is change in some child class's needs. This definitely violates dependency inversion principle.
Better way to do that can be like this. We have createFields() method in base class which adds up components to some collection. Order of the components in collection determines order of the corresponding fields on the panel. And our need is to add field somewhere on the panel for specific need.
So in base class we can now add method addComponentAfter().
/**
* Adds given component with label "label" after given index
*/
Private addComponentAfter(index, label, component)
{
labels.add(index + 1, label);
components.add(index + 1, component);
}
I defined new child class for this scenario Child1PanelClass.
Public class Child1PanelClass()
{
@overrides
Protected void createFields()
{
super.createFields();
// create field specific for this need
// add it after index
addComponentAfter(index, label, component);
}
}
So I have overridden createFields() and let my base class add fields in its way. After that added my new field in required order.
With this my base class doesn't need to be changed for any further change in some other flow. Also we have encapsulated the change for this flow and limited it to its own class.
This suffices Encapsulate the Change principle.
There can be question of base class changing order of components. Which may affect child class as it relies on ordering of fields decided by base class. However this dependency is not harmful as child class are always supposed to depend on their base. Which sounds obvious also. This is what called as Dependency Inversion.
To Summarize, There are some principles that should be followed when there is extension required to system. Decision of whether to use flags, some protected methods or subclassing with dependency inversions depends really upon many factors like time availability for the change, future change anticipations, extent of reuse etc. We have to consider all these factors before really deciding with the approach. But I prefer to do change which are following open-closed principle, dependency inversion, encapsulate the change principles. Because what we all need is system which is more stable and at same time easier for extensions.
No comments:
Post a Comment