The situation in which you have to modify a lot of files to add a new feature is a classic violation of the Open/Closed Principle:
source: https://dzone.com/articles/the-power-of-openclosed-principle?edition=155254&utm_source=Weekly%20Digest&utm_medium=email&utm_campaign=wd%202016-12-28Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
If we were to adhere to this rule, there would be no files to modify in order to add a new feature or those files would be limited to some configuration/factory methods.
Applying OCP
To apply the principle, you need four keywords: abstract, extends, interface, and implements. You find the places that require a change:
class Report {
enum Type {
ORDERS_PER_DAY, CONVERSION_RATES
}
Type type;
String generate() {
...
switch (type) {
case ORDERS_PER_DAY:
// do stuff
break;
case CONVERSION_RATES:
// do stuff
break;
}
...
}
}
Then you extract proper abstractions:
interface Report {
String generate();
}
Then implement each of your features using these abstractions in separate source files:
class OrdersPerDayReport implements Report {
public String generate() {
// do stuff
}
}
class ConversionRatesReport implements Report {
public String generate() {
// do stuff
}
}
The interfaces or base classes are the ones being both open and closed
– they’re open to be derived from and closed for modifying their content.
The real challenge in implementing the Open/Closed Principle lies in choosing the places to close (as in “closed for modification”).
What are the heuristics?
- Product backlog: if the new feature is already present in the backlog, there’s a huge chance it will be implemented one day. Therefore, if we’re working with the potentially affected code, we might prepare it for the upcoming feature.
- Previous features: I’d call this “closing by refactoring”. While implementing new features, whenever you notice that a place is vulnerable to modifications, you close it, so that the problem doesn’t occur ever again.