5.8 KiB
+++ title = "The Types of OOP" date = 2022-05-31 draft = true
[taxonomies] tags = ["programming", "oo"] +++
I'm seeing that a large group of newcomers don't get the idea behind object oriented programming. And I think this misunderstanding is basically the way this is explained.
If we take a page from the classical "first look" at OOP, we would see something like
class Animal:
def say(self):
print("Don't know what to say")
class Dog(Animal):
def say(self):
print("Woof!")
The example usually creates a base class, with a method (function), extends the base class in a child class and overrides the method. And that's basically it.
But, in real life, things are not quite like that.
Ok ok, there are way more examples of things written in real life OO beyond the ones I talk here, but in my experinece they are not that common. Of course, your mileage may vary.
So, what do I mean by "Types of OOP" in "the real world"?
The Grouping
One of the types of OOP we usually do is "grouping". What do I mean by that is that we usually have a group of values that are related and should be worked together.
For example, in stock markets, we have the concept of a "candle": The candle summazes what happened in a period of time -- be it one day, one hour, one minute of 10 milliseconds. In that space of time, we capture the first value seen, the last value, the highest value and the lowest value. We could, for example, use a series of arrays for that, like
first_values = [10, 20, 14, 21, 17]
last_values = [20, 14, 21, 17, 12]
high_values = [30, 20, 21, 21, 18]
low_values = [9 , 14, 13, 17, 12]
Here, each "column" represents one entry in the period time; if we are doing it per hour, that means in the first collected hour we have the values in the first column, one hour later we have the values in the second column and so on. Thing is, those values are related, and we could basically group them together.
class Candle:
def __init__(self, first, last, high, low):
self.first = first
self.last = last
self.high = high
self.low = low
entries = [
Candle(10, 20, 30, 9),
Candle(20, 14, 20, 14),
Candle(14, 21, 21, 13),
Candle(21, 17, 21, 17),
Candle(17, 12, 18, 21),
]
This is basically the same thing, but now instead of having multiple arrays, we
have just one that keeps all related information together. The concept of
grouping information like that is not something new and even C had a struct
to
keep related values together. But when we use objects, we can also add
"behaviours" to our data, and we could ask if the candle is going up or down, if
that would help us in our requirements.
A simple example could be the name of a person
class Name:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
def full_name(self):
return "{} {}".format(self.first_name, self.last_name)
For a person, we have related information like the first name and last name. But if we want to put their names in an envelope, we need to print both names together, so we added a "behaviour" (method) to produce that for us.
Quite simple.
The Façade
Façade is, actually, a design pattern: You want to hide what the code does behind the curtain, so changing the underlying structure would not affect the code.
For example, let's take our Name example before. Let's say we want to save it. We could do
class Name:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
def full_name(self):
return "{} {}".format(self.first_name, self.last_name)
def save(self):
# saving to disk
with open(self.full_name(), 'w') as target:
target.write("first_name: {}\n".format(self.first_name))
target.write("last_name: {}\n".format(self.last_name))
All good, we are just keeping our names saved on disk, with the filename being the full name and, inside, we keep the first and last names. But let's say our disk is full, and someone thought a good replacement would be to store all those files in S3. Later, someone came with the great idea of saving those in a database. And, much later, we realized we can speed things up by saving the content in a Redis store for faster retrieval. In all those changes, we exposed how things are saved inside our class, which may be simple enough, but imagine you have hundreds of things to be stored, with their classes, and now you have to go there and replace every one of those classes for every time we thought we found a better way to store it.
In this case, we could've come with a Storage
class, which would hide the way
we are storing (and probably retrieving, although I haven't shown in this
example) from the classes, so our save()
method could be
def save(self, storage):
# convert to some format `Storage` knows, or just break the content of the
# class down in some dictionary, for example.
storage.save(self)
The Storage
is just a façade to do the way we are actually saving the
data. The class doesn't know how its contents are stored, but it also doesn't
have to worry about it. Storage
may even contain some related information to
save the content properly, but it's real purpose is just to hide whatever is
under it so the rest of the system doesn't have to worry about it.
{% note() %} For languages that have support for it may use "interfaces" or "traits" to ensure that whatever the Storage is doing, it has the same appearance everywhere. {% end %}
The Framework
The last common OOP design I usually see is what I can call "the framework". Usually, projects are not written in this style, but the framework they use allows this.
The framework is focused on having lots of methods in some class that you extend and override a couple of methods. For example, if you are using Django, you may use