OOP: how I would explain it to my grandmother
Back in the days, computers were able to run lists of operations (called “programs”). We had some popular programming languages like Fortran, Cobol, and Basic. The programming paradigm used was “Procedural programming”. This paradigm involved the following statements:
- The data of the program is stored in containers, so-called variables
- The way as the data is handled is described by so-called functions
A program was a bunch of functions and variables, and this led to some consequences:
- The code was difficult to maintain
- The entire program was monolithic
- Changes to some portions of the code often did break other code parts
- There was no easy way to reuse the code for other programs
Due to these inconveniences, quick often the programmers tended to produce the notorious spaghetti-code.
So, we felt the need to adopt a more versatile approach, and thus OOP (Object Oriented Programming) comes in help for us.
OOP was introduced in 1967 by the Simula programming language, followed by other languages like Smalltalk and Lisp. By the way, the real “game-breaker” was introduced by Bjarne Stroustrup, with his C++.
How OOP facilitates our (dev) life
Since this is a beginner lecture, we don’t want to use technical terms. Furthermore, it’s plenty of good resources on the Internet, where one can learn the principles of OOP. The scope of this lecture is to give a basic yet explanatory overall vision.
1. Stylize it with Class
Classes are another name for Abstract Objects.
Let’s suppose to have a Car. A Car can accelerate, brake, and steer.
Furthermore, a car has four wheels and a gear shift. Now, let’s suppose to have a Scooter. A Scooter can accelerate, brake, and steer as well. It only has two wheels but no gear shift at all. But, hey, both Car and Scooter are two Vehicles! They both share some variables (or properties), i.e.: the wheels, some functions (or methods), i.e.: accelerate, brake, steer. Also, the Car has some stuff (property: Gear Shift) that the Scooter does not own.
We could think to write three Classes:
- The Vehicle Class
- The Car Class
- The Scooter Class
We put all the common stuff in the Vehicle Class (the ability to accelerate, brake and steer, plus the fact that a Vehicle has a certain number of wheels).
The Car and the Vehicle Classes inherit from the Vehicle Class. So, they inherit its methods and variables, being able to redefine (or override) some of them (i.e.: we know that a Vehicle has a certain number of wheels, for sure, but we don’t know in advance how many they are, this will be declared or, better saying: implemented in its Child Classes).
To visually summarise, we could write some meta-code like this:
Class Vehicle {
Property: numberOfWheels;
Function: accelerate();
Function: brake();
Function: steer();
}Class Scooter that inherits from Vehicle {
initialize {
numberOfWheels = 2;
}
}Class Car that inherits from Vehicle {
initialize {
numbersOfWheels = 4;
}Property: gearShift;Function: changeGear();
}
By doing this way, we can see that we don’t need to declare anything on the Scooter Class, due to the fact it inherits all properties and functions from the Vehicle Class. While inside the Car Class, we have all the stuff from the Vehicle class, plus we have to specify the extra stuff that belongs only to the Car Class.
We also initialize our Class: every Car has four wheels, every Scooter has two wheels.
Later, we can access our properties and call our functions (or methods), using the so-called dot notation.
Polo = new Car() ← we are creating a new Instance of the Car Class
Polo.gearShift.mode = manual
Polo.accelerate()
2. Give me an interface and I will move the Car!
Let’s suppose to zoom in our Car. If we are manufacturing a real car, we would like to insert and assemble the gears, the engine, the pistons, every single screw and so on. But, it would be dangerous and also very unuseful to give the car’s buyer access on every single piece of our Car. Rather, we’d like to prefer to give him access to the higher-level controls only! To be clearer, if a driver wants to steer, he doesn’t manually insert his hand into the car’s mechanics to move the wheels, he only needs to turn the car’s steering wheel! In other words, the steering wheel acts as an interface between the driver and the car. This mechanism is a pillar in OOP and it’s called Encapsulation. It allows developers to abstract a concept, and at the same time, it allows the end user to do a task with minimum effort and minimum risk because the developer already implemented the behavior when he developed the interface. So, when we call accelerate(), we are done! Under the hood, the developer has implemented the logic to accelerate, maybe involving a plethora of actors (gears, pistons and so on). But we only have a high-level way to accelerate: invoking accelerate(). Another example could be at the Post Office: the employee at the front office could be an interface between us and the withdrawal of some cash. We can’t manually go in the back office and withdraw money (unless we want to be arrested, obviously!), so we need an interface that does the work for us. Another way of reading: I don’t know the processes that are involved to brake, but I know that if I call the function brake(), my car will brake for sure.
3. If one is good, more is better
Let’s suppose to have a Class with a function sum(). This function accepts two integer numbers and returns the sum. Later on, we want to code another function that sums two double numbers. In procedural programming, we should create two different functions, named differently, i.e.: sumInteger() and sumDouble(). With OOP we can use Polymorphism and overload the sum function.
Class MathOperations {
static int sum(int a, int b) {
return a + b
}
static double sum(double a, double b) {
return a + b
}
}(The decorator static means that the function is accessible without creating an instance of the class)Later...print(MathOperations.sum(3, 4)) <-- Result is 7print(MathOperations.sum(0.9, 0.1)) <-- Result is 1.0
We can see how the right sum function is chosen automatically: the compiler (or the interpreter) checks for the input type. In the first case, the signature is the one from the integer sum function. In the latter case, the double sum function is chosen, since we passed two double values to the function.
This enhances flexibility when we write down our code.
Conclusions
Well, we reached the end of our journey. The purpose of this article is to give to the reader a high-level understanding of what is OOP and what advantages it offers when compared to procedural programming. If one wants to go deeper, it’s plenty of good books on the Internet. Furthermore, here on Medium, there are really good stories about it. Thanks for your time and…
Mike.greet(all);
;-)