👋 Goodbye, Object Oriented Programming👋
I have been writing computer programs using Object Oriented languages for many years. The first language I used was C++, then Smalltalk, and finally .NET and Java. I was very enthusiastic to take advantage of the features of Inheritance, Encapsulation, and Polymorphism, which are the three main principles of this type of programming. I was looking forward to being able to reuse code and benefit from the experience of those who had gone before me in this new and exciting field. However, I soon realized that things were not as simple as I had expected.
Inheritance is the first thing to be lost or given up
At first, it seems that the main advantage of Object Oriented Programming is Inheritance. It makes sense when we look at examples of how different shapes are related to each other, which are often used to explain the concept to people who are just learning about it.
I was so excited about the idea of reusing things that I immediately went out and shared it with others.
Banana Monkey Jungle Problem
I was feeling determined and motivated to solve my problems, so I started creating class hierarchies and writing code. I was excited to use the concept of reuse by taking an existing class from a previous project.
I thought it would be easy, but I soon realized I needed the parent class, the parent's parent, and all of the parents of the contained objects. Despite the difficulty, I was determined to keep going and eventually, I was able to get everything working and I felt a sense of accomplishment.
There’s a great quote by Joe Armstrong, the creator of Erlang:
The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.
Banana Monkey Jungle Solution
I can solve this issue by not making hierarchies too complicated. However, if the inheritance is the best way to reuse code, then any restrictions I put on it will reduce the advantages of reusing code.
Is that correct?
What should an object-oriented programmer who believes in the benefits of this approach do?
The answer is to contain and delegate.
I will explain this further later.
The Diamond Problem
Eventually, this difficult and possibly impossible problem will appear.
Most object-oriented programming languages do not allow you to use the same method name for two different tasks, even though it would make sense to do so. So why is it so hard for object-oriented languages to support this?
Well, imagine the following pseudocode:
Class PoweredDevice {
}
Class Scanner inherits from PoweredDevice {
function start() {
}
}
Class Printer inherits from PoweredDevice {
function start() {
}
}
Class Copier inherits from Scanner, Printer {
}
The Copier class needs to decide which start function it will use, the one from the Scanner class or the one from the Printer class, since it cannot use both.
The Diamond Solution
The answer is easy: don't do what you are trying to do. Most object-oriented languages do not allow it. If you need to model it, you should use containment and delegation.
Class PoweredDevice {
}
Class Scanner inherits from PoweredDevice {
function start() {
}
}
Class Printer inherits from PoweredDevice {
function start() {
}
}
Class Copier {
Scanner scanner
Printer printer
function start() {
printer.start()
}
}
The Copier class now has both a Printer and a Scanner inside it. It is using the Printer class to do the start function, but it could just as easily use the Scanner class instead. This shows that Inheritance is not always the best way to solve a problem.
The Fragile Base Class Problem
I had been making my data structures simpler and avoiding any loops, but then one day my code stopped working even though I hadn't changed it. I realized that something had changed, but it wasn't in my code. It was in the class I had inherited from. I was confused as to how a change in the base class could break my code. To explain, imagine a base class written in Java that looks like this...
import java.util.ArrayList;
public class Array
{
private ArrayList<Object> a = new ArrayList<Object>();
public void add(Object element)
{
a.add(element);
}
public void addAll(Object elements[])
{
for (int i = 0; i < elements.length; ++i)
a.add(elements[i]); // this line is going to be changed
}
}
Warning: Pay attention to the line of code that has been marked as a comment. We will be making changes to this line later, which could cause problems. This class has two features: add(), which adds one item, and addAll(), which adds multiple items by calling the add() function.
And here’s the Derived class:
public class ArrayCount extends Array
{
private int count = 0;
@Override
public void add(Object element)
{
super.add(element);
++count;
}
@Override
public void addAll(Object elements[])
{
super.addAll(elements);
count += elements.length;
}
}
The ArrayCount class is a type of Array class that keeps track of how many elements are in the array. The Array class has two methods, add() and addAll(), which add elements to the array. The ArrayCount class also has these two methods, but it also increments the count each time an element is added.
We are making a change to the code in the Base class. The line of code that was commented out is now being changed to the following.
public void addAll(Object elements[])
{
for (int i = 0; i < elements.length; ++i)
add(elements[i]); // this line was changed
}
The owner of the Base class is unaware of the Derived class and its changes to the add() function. When the addAll() function is called, the count is increased twice, once by the Derived class's add() and once by the number of elements added in the Derived class's addAll(). This is unexpected and the owner of the Derived class will be surprised.
IT’S COUNTED TWICE
If a Derived class is created from a Base class, the author of the Derived class must be aware of how the Base class is implemented and must be informed of any changes made to the Base class, as these changes could cause the Derived class to break in unexpected ways. This is a major issue that could cause the Inheritance pillar to become unstable.
The Fragile Base Class Solution
Using Contain and Delegate, we can switch from White Box programming, where we have to look at the implementation of the base class, to Black Box programming, where we don't need to know the implementation since we can't inject code into the base class. This makes it easier to focus on the interface. However, this trend is concerning because Object Oriented languages were designed to make Inheritance easy, but Contain and Delegate is not easy to do. This should make us question the power of Classification via Hierarchies.
The Hierarchy Problem
Whenever I start a new job, I'm not sure how to organize my documents, like the employee handbook. Should I create a folder called "Documents" and then a folder called "Company" inside that?
Or should I create a folder called "Company" and then a folder called "Documents" inside that?
Both work, but which is the right way to do it?
The idea of Categorical Hierarchies is that there are general categories (parents) and more specific categories (children) that are based on the general categories. But if the parent and child categories can be switched, then the model isn't working correctly.
The Hierarchy Solution
In the real world, we see Containment (or Exclusive Ownership) Hierarchies everywhere, but not Categorical Hierarchies.
For example, a sock drawer is contained in a dresser, which is contained in a bedroom, which is contained in a house. Directories on a hard drive are another example of a Containment Hierarchy. To categorize things, it doesn't matter where we put them.
For example, we can put company documents in a folder of Documents or a folder called Stuff.
I label the file with words that describe it so I can easily find it later. The words I use are:
Document
Company
Handbook
Tags can be associated with a document in any order and do not have any hierarchy. This means that the problem of multiple types associated with a document, known as the Diamond Problem, is solved. This also means that the concept of Inheritance is no longer necessary.
Goodbye, Inheritance.
Encapsulation is the second concept to be understood or accepted
Object Oriented Programming has many benefits, and Encapsulation is one of the most important. Encapsulation means that the state variables of an object are protected from outside access, so we don't have to worry about global variables being accessed by anyone. Encapsulation is a great way to keep your variables safe. It is an incredible feature and should be celebrated!
The Reference Problem
When passing an Object to a function, the function will not pass the Object itself, but instead will pass a reference or pointer to the Object. This reference can be stored in a private variable which is protected by Encapsulation. However, the Object is not safe because the code that called the function must have a reference to the Object in order to pass it to the function.
The Reference Solution
The Constructor will need to make a copy of the given Object, but not just a shallow copy, it needs to be a deep copy, meaning it needs to copy every object inside the given Object, and every object inside those objects, and so on. This is not very efficient. Additionally, not all objects can be copied, as some of them have resources associated with the operating system, which makes copying either useless or impossible. This is a problem that all mainstream object-oriented languages have. This means that encapsulation is not possible.
Goodbye, Encapsulation.
Polymorphism, the Third Pillar to Fall
Polymorphism was not as important as the other two elements of Object Oriented programming, like Larry Fine of the Three Stooges. It was still useful, but you don't need an Object Oriented language to get it. Interfaces can provide the same benefits without the extra complexity of Object Oriented programming, and they can be used to create more varied behaviors. So, we are saying goodbye to Object Oriented Polymorphism and hello to interface-based Polymorphism.
Broken Promises
Object-oriented programming promised a lot when it first came out, and it still promises a lot to inexperienced programmers who are learning about it. It took me a long time to realize that object-oriented programming was not as great as it seemed. I was inexperienced and trusting, and I was disappointed.
Good-bye, Object Oriented Programming.
So then what?
Hello, Functional Programming. It has been a pleasure to collaborate with you over the past few years.
I do not trust your promises and I need to see proof that they are true before I believe them.
This means that if you have been hurt or disappointed in the past, you will be more cautious in the future.
You understand.
Mình hy vọng bạn thích bài viết này và học thêm được điều gì đó mới.
Donate mình một ly cafe hoặc 1 cây bút bi để mình có thêm động lực cho ra nhiều bài viết hay và chất lượng hơn trong tương lai nhé. À mà nếu bạn có bất kỳ câu hỏi nào thì đừng ngại comment hoặc liên hệ mình qua: Zalo - 0374226770 hoặc Facebook. Mình xin cảm ơn.
Momo: NGUYỄN ANH TUẤN - 0374226770
TPBank: NGUYỄN ANH TUẤN - 0374226770 (hoặc 01681423001)
All rights reserved