OOP: Is it really the problem?
When developers these days hear or read about Object-oriented programming, or OOP as it is commonly abbreviated, the cringe factor is more than palpable. Even if you don’t fully understand what people are talking about, you can still feel the disdain this paradigm has garnered. But why? What’s wrong with OOP to make so many developers run back to functional programming (FP) en mass? Yes, “back to”. FP has been around for quite some time, but it didn’t always have a name.
So what’s really going on with OOP? Why is it receiving so much hate? Honestly, I don’t know. I’ve ready the articles and books that talk about how the promises of OOP have failed to provide any real benefit. And every time I read one of those articles, I always end up thinking the same thing: “These people don’t know how to use OOP!”
Somehow, I can see myself catching a lot of hate for that last sentence, but it’s truly what I think. Maybe if we do a quick review of the 3 tenets of OOP, I can show how I’ve come to that conclusion.
Tenet 1: Inheritance
The big thing about inheritance is code re-use. When used properly, inheritance can allow for the development of new extensions to code very quickly, without having to rewrite any common parts. When done carefully, inheritance can be used to craft entire reusable libraries of code. Does that mean it’s a flawless approach? Of course not!
- Complaint #1: The Diamond Problem
This happens when 2 or more classes inheriting the same base class are themselves used as the base classes for yet another subclass via multiple-inheritance. It becomes unclear what function will be called when 2 or more of the parent classes contain a function with the same name. - Complaint #2: Base Fragility
Changes to the implementation of base class functions can easily affect subclasses in unexpected ways. - Complaint #3: The Banana, Gorilla, Jungle Problem
In order to copy a class from one project into another, you must also copy the classes for all “is-a” and “has-a” relationships to all classes in the inheritance chain of that class. Or in terms of the analogy, to get the banana, you must also bring along the gorilla holding it and the jungle it lives in.
Understanding the 1st Tenet
So yes, there are issue, but are they really that much of a problem? Java left multiple inheritance out because of #1.
Dealing with the diamond
Frankly, I’ve never considered it a problem. The developers of the STL in C++ didn’t seem to mind it so much either. Anyone use IOStream? The solution to the problem depends on your language. For C++, just use the ::
operator to select the method you want.
class Animal {};class Horse: public Animal
void ride(Person &p) {};
}class Donkey: public Animal
void ride(Person &p) {};
}class Mule: public Horse, public Donkey
void ride(Person &p) {
Horse::ride(p);
};
}
But let’s be honest with ourselves for a moment. Does a mule really have all the properties of a horse and all the properties of a donkey? Somehow, I don’t think it would be nearly as valuable if it did. So it’s not proper to construct a mule this way even if the real thing is analogously created.
That’s the problem with complaint #1. In most cases of the diamond inheritance pattern, that was a bad idea which might have been better solved by complete inheritance by encapsulation (composition) instead of extension, or a mixture of the two modes. In the above example, Mule might better have been implemented like this:
class Mule: public Horse {
private:
Donkey d;
};
Done this way, it’s clear and obvious that Mule::ride == Horse::ride
. Mule is also free to use any public properties of Donkey. Problem solved. Just because you can use a butterknife to open a paint can doesn’t mean that you should. Likewise, don’t blame the butterknife if it now has paint on it.
What about the base fragility problem?
This one is a real problem with inheritance. Take a look here for a good example of the fragility issue. That would have been a lot of copy and paste. So please excuse me for taking such a shortcut. In fact, you can read that article in full before coming back here because it does a pretty good job of explaining the problems of OOP.
This is yet another case where we need to understand what we’re doing before we build. Consider a base class to be a foundation. If you buy a car that uses 225/65R15 tires, and always replace your tires with that size, your speedometer will report reasonably accurately. But don’t be surprised if after replacing your tires with 225/65R16 that you’re being ticketed for speeding despite what your speedometer says.
Let’s put this another way. Even functional programming suffers from a version of this problem. In that case, it might be more apt to call it a “fragile path problem. In FP there are 2 types of actions that a function can take before returning something: a)directly manipulate parameters, b) call another function. If you looked at the example I referenced, you’d notice that the implementation change was analogous to an FP path change.
Code that wasn’t expected to get called is now getting called. This problem isn’t unique to inheritance. It’s a problem with programming in general. So why is this being used as a strike against inheritance, and therefore OOP? It’s nothing new.
The banana, gorilla, jungle problem is old too!
This problem is as old as the concept of development libraries themselves. It started with assembler macros. Those macros eventually became patterns of use that were bundled into libraries to simplify development over those patterns. Eventually those patterns were cemented into various languages with keywords to trigger those patterns. New patterns were created on top of those languages, bundled into libraries, and cemented into newer and newer later-generation languages.
High-level languages these days are little more than the banana, gorilla, jungle problem in a more acceptable form. Back in the day, we called this problem “code bloat”. We were forever dragging around code we didn’t want so that we didn’t have to re-write the code we did want. Developers today are still doing the exact same thing. How much of the .NET runtime do you really need installed to make your .NET app work? What percentage of the Spring Boot libraries is your java app truly using? For the most part, developers these days don’t know and don’t care. They’re just happy they didn’t have to learn about and rewrite the portion of the code they needed.
Is this unique to inheritance? No. It’s called a dependency, and regardless of your coding style, unless you’re writing everything from scratch yourself, you will have dependencies. This is not an OOP problem.
Tenet 2: Encapsulation
This seems to be one of the most poorly understood things about OOP. Some if not many seem to think that encapsulation implies some kind of data security. From those trying to tear down encapsulation, you’ll hear arguments like:
- Complaint #4: The Unprivileged Access Problem
If I create an object, then pass that object as a parameter to a function of an instance of some class, and that function saves the data in a private or protected field, the object gets encapsulated by the class instance. However, I can still modify the object.
Understanding the 2nd Tenet
This meager complaint comes from a misunderstanding of the purpose of encapsulation. Let’s clear that up first. Encapsulation has only 2 jobs:
- Keep the data close to the logic that will manipulate it.
- Keep external logic from manipulating the encapsulated data through the encapsulating instance in ways beyond the control of that instance.
That emphasized part of the 2nd job is the thing that is most often missed in developer’s understanding. There’s nothing in these 2 jobs that even comes close to hinting at security unless you omit that preposition.
By now, you’ve probably already figured out what I’m going to say about complaint #4. It isn’t an issue. Never was. The actual issue has always been with developers trying to depend on encapsulation to provide security promises. Data security is a far more complex topic than encapsulation. You have a better chance of grilling a snowball without melting it than you have of creating a solid security policy around mere data encapsulation. Unless the encapsulating class generates the data on its own, there’s always a way to get between the data source and the encapsulating class.
Tenet 3: Polymorphism
Oddly, the problem most often raised with polymorphism isn’t actually a problem with polymorphism at all. In fact, this is a well-liked, often used concept. So what’s the complaint?
- Complaint #5: Ubiquity
Polymorphism is a concept that can be provided for in many different ways. This is clear simply from the many different approaches provided for in so many different languages. As such, it is not sufficient to support the use of OOP by itself, since non-OOP approaches can also provide for it.
Understanding the 3rd Tenet
How do I put this? Complaint #5 is correct. At the same time, is it relevant? Encapsulation can be (and often is) achieved using a modular approach, so it has ubiquity just like polymorphism. The code-reuse capability of inheritance can be (and often is) achieved using a combination of the modular approach and libraries. So again, there is ubiquity. Does that mean that ubiquity is an argument against all 3 tenets? Only if it’s an argument against all 3 tenets in every design pattern they appear in. Short of this, it’s not even a good argument.
Is OOP really the problem?
OOP should just be a tool in your tool drawer that you pull out when it’s the best tool for the job. Whether dictated to your by your boss, or you’re working on a project where you’re in control, you should use it if it best suits your needs, and leave it alone when it’s not a good fit. The same goes for FP and any other useful development paradigm. How is it ever the fault of the tool if the user doesn’t use the tool the way it was designed to be used?
Somewhere along the way, we developers have lost our way. We’ve gone from trying to prove ourselves to be experts by finding the best ways to use the tools at our disposal, to expecting the tools to provide the expertise and force us into doing things the “right way”, whatever that is. This is rather unfortunate for development in general.
What will happen to development when the generation that held actual expertise finally decides to pass the baton to the generation that is expecting the expertise to be contained in the tooling? As technology changes, new tools will be needed. Yet, with the prevailing trend, it’s questionable whether anyone with the expertise required to write those tools will be available to do so.
Software development is truly a field where knowledge of the past is an absolute requirement to create the future. If you’re finding that you’re having difficulty using a certain tool, at least one of 2 things must be true. Either you don’t know how to use the tool properly, or it’s the wrong tool for the job. Lately, I think we’re entirely too quick to jump to the latter conclusion. Maybe it’s because accepting the former as true requires us to take responsibility.