OOP is the kind of thing that looks neat when you read about it in a schoolbook example.
Here using Java, because every competent programmer knows how to read it.
Animal.java
public abstract class Animal {
public void walk() {
//Enter walking code
}
}
Dog.java
public class Dog extends Animal {
public void bark() {
// Enter barking code
}
}
At which point we all think it’s so cool that Dog
inherits the walk
method just from simply being an Animal
, so whenever we add Cat
, an Animal
that does not bark()
, we don’t need to write the code for walk()
again. All is good, we praise the teacher for bestowing this knowledge upon us and go home, contemplating how to best apply this to ruin the next or current codebase in our lives. Object oriented programming suffers from the following:
It’s best applied in larger codebases, because in small ones you didn’t really need to repeat yourself from the beginning.
It’s best applied in small codebases, because in the large ones you will scramble code all over the place and maintainers will spend most of their time looking for everything.
Once you start noticing FactoryFactories or multiple layers of inheritance, some layers which override methods and some that don’t, then you know you have a problem. Say you saw a method you wanted to know more about, so you instructed your trusty IDE to point you to it, but it hit a dead end in an interface
somewhere and you have absolutely no idea which implementation you’re going to, since the “show implementations” menu exceeds your monitor. It’s easy to consider giving up OOP-languages entirely at this point, but did you know that middle grounds exist? There’s no need for an interface for every class, you aren’t going to need it. The day when you refactor in a second possible class for doing the same thing won’t come, but if it does, it’d the day when you know for sure what needs to be in that interface.
Don’t create a Factory
or a Builder
when a simple constructor will do. The more classes you can imagine being simple C-style structs, the better. Just don’t go out on a crusade for eliminating every single piece of object oriented code in your codebase just because you hate it, because that’s exactly how you ruin a codebase (That’s what brought you here!)
Besides not needing to repeat yourself, a big selling point of OOP is encapsulation. The idea is that code is stowed away into class methods, called on the instances themselves, and put away into packages, so the total amount of available function calls in a set piece of code is lower than it would have been if you were using, say, C.
It is convenient, because you can sort of “feel your way” through a codebase you’re working in by using your IDE to probe the objects to see which method calls are available and which arguments they want. In a way, this is how you create self documenting code.
The problem is when people don’t use these tools enough. You end up with classes with long names like ProjectContractChargingPeriodProjectAccountReferenceVM, or packages with several classes inside that require each other, but they are all public
facing. I have absolutely no idea from reading that class name what it’s supposed to be. Most likely you shouldn’t need to expose this complexity through a class name, unless you’re trying to make a point to your architect. I have even witnessed classes, sitting under several layers of packages, which each package name prepended to the class name. Don’t ever do this! You have this information available in the namespace.
You think to yourself, this is not a problem. I’ll hide away this monster of a class, let it implement a public interface ChargingPeriod
and some cool factory somewhere will decide from the inputs when to return this, and anyone implementing this code will only see ChargingPeriod
and ChargingPeriodFactory
. Sweet!
Except it isn’t, because after some incredibly puzzling behaviour in the code you are now trying to debug a very specific usecase and your IDE says tough luck, because it can’t just automatically direct you to the code that actually gets called when you do something:
private final ChargingPeriod chargingPeriod = factory.build(params);
chargingPeriod.put(moreParams); // <--- this broke your shit
Inspecting the interface reveals just how dire your situation is, because it leads to potentially hundreds of implementations, all with equally horrible names. Do you expect the factory to be any better, with the complex machinations that allow you to use supply your parameters and it will conjure the appropriate implementation? Don’t even get started with the possibility of multiple factories being used… Your little trick just made your life (or someone elses) harder, by magnitudes.
Somehow, people who couldn’t hide away their terrors by encapsulation detected these issues much sooner than the people who could! So now you’re wondering what the take-away is here and if we want more or less encapsulation. In a way, we want more, but we don’t want to try using it like some silver bullet to escape having to design our code in a way that it makes sense from a programming perspective, not a business perspective. It sounds familiar, because that exactly describes the problem with object oriented programming.