Object-oriented programming: the basics

avatar icon blog author
#TeamSpeednet

Note: this article was originally written in Polish by Tomasz Hanc. Due to the great interest in the topic, we decided to translate it into English and share it with the worldwide developer community. 

Object-oriented programming has been around for many years. Wikipedia states that since the mid-1980s, OOP has become the dominant technique in the programming world. Every backend job ad includes object-oriented programming (OOP) knowledge as an essential requirement to apply for the position. Is object-oriented programming just turning everything into an object? What should be in it? 

Table of contents

    1. Let’s start with the basics 
    2. Abstraction 
    3. Encapsulation
    4. Polymorphism
    5. Inheritance
    6. All about that communication
    7. Access modifiers and responsibility 
    8. DRY - Don't Repeat Yourself
    9. Structure Duplication 
    10. Method duplication 

Let’s start with the basics 

I assume everyone knows the difference between an object and a class. There is no need to explain such basics, but a little reminder might come in handy. One of the most popular questions during job interviews is the one about the basic assumptions of object-oriented programming. There are four such assumptions: abstraction, encapsulation, polymorphism, and inheritance.  

At first glance, all these assumptions are necessary and make object-oriented programming make sense. Once, during an interview, I heard a question: "If you were to give up one of the assumptions of object-oriented programming, which would it be and why?" The question was purely hypothetical of course - we will not give up anything… but it is worth considering. 

Abstraction 

Abstraction is some kind of generalization, most often presented as an abstract class or interface. Virtually all design patterns and programming principles are based on abstraction. If we create dependencies in the code, we want to depend on abstraction, not on a specific implementation. By removing this assumption, we limit ourselves a lot. Abstraction is quite important – thanks to it we can ensure the extensibility of our application or we can reduce unnecessary dependencies. 

Encapsulation 

Also known as hermetization, which is hiding an implementation. The simplest application of this assumption is to declare variables as private. We don't want to give access to the details outside. We want to have hermetically sealed objects that will not be damaged by accident. Is it possible to live without encapsulation?  

Probably yes, but you have to be careful. It can be done though. Is it prudent? I would argue with that, although various libraries "force" us to do so anyway by requiring getters and setters for all fields of the object. 

Polymorphism  

Polymorphism is a mechanism that is strongly related to abstraction. Thanks to it, we know what is to be done, not how. How it is done will be determined when the code is executed. Thanks to polymorphism, we can reverse dependencies. Without polymorphism, there is no room for abstraction. Nowadays we can easily use polymorphism - in the past, a similar effect was achieved through pointers to functions (C language). 

Inheritance 

When I was learning what object-oriented programming was, inheritance was the most important thing for me. Code that was repeated could be crammed into the superior class and there was no "duplication" of it. However, now I think a little differently. We use inheritance mainly to extend the behavior of an object. Is there any other way to do this?  

Yes, it exists, and that is composition. Therefore, if I were to indicate one of the assumptions to be removed, I would choose inheritance. To be honest, a misused inheritance is more of a hassle than a help. This may be a bit controversial, but without inheritance, we are still able to code fully object-oriented. 

All about that communication 

When designing new applications or functionalities, we often spend a significant amount of time planning the structure. We think about what fields the database table should have, which we then reflect in the class code. With the help of modern Integrated Development Environments (IDEs), we automatically generate getters or setters and the class code is ready. On the other hand, we spend very little time planning communication between objects.  

In OOP, it's not the structure that is most important, but the communication. How objects communicate with each other is crucial. Objects can use structures, but this information should be hidden (encapsulation). The object should have behavior, not just data. How does such an anemic entity differ from a PHP array or a Java HashMap? 

Access modifiers and responsibility 

We all know what access modifiers are for. But have we thought about the responsibility we place on ourselves, for example by using a protected modifier instead of a private one? Overall, the private modifier is the best in terms of security. Any class that would inherit from ours has no access to anything private. 

Thanks to this, we do not have to worry about anything when we want to change the name of this method, delete it, or divide it into several smaller methods. There is no chance of breaking any other code that uses ours.

When we go down a step and give our method the protected modifier, we have to consider the classes that inherit from ours when we change something. It is still relatively safe, but please do reflect on it and be careful – especially when making your code available for general use. 

When assigning the public modifier, in addition to child classes, we must remember each call of the given code. Therefore, the lower the access modifier, the greater the responsibility. We also need to be sure that our code is correct – more entities become dependent on it. Therefore, when we make a change in one place, we can mess up something in the other, that was supposed to be completely independent of the first one. 

DRY - Don't Repeat Yourself 

„Avoid code duplication!” – I heard it many times during my studies. The duplication problem was often been solved by inheritance or by creating Utils classes. But what duplication is really about? 

Structure Duplication 

Suppose we have two objects that have similar fields. Does this mean that we should create a parent class that will be the common part of these objects? This may be advisable at times, but most often it will be the wrong solution:  

class Human
{
    private $name;
    private $dateOfBirth;
}

class Dog
{
    private $name;
    private $dateOfBirth;
}

This example may be exaggerated, but remember that a similar structure is not always a duplication. 

Method duplication 


class Basket { public function addProduct($product) { if (3 == count($this->products)) { throw new Exception("Max 3 products allowed"); } $this->products[] = $product; } } class Shipment { public function addProduct($product) { if (3 == count($this->products)) { throw new Exception("Max 3 products allowed"); } $this->products[] = $product; } }

What about the methods? Unfortunately, it depends. What do the business rules say? Is the maximum number of products in the basket and shipping depend on each other in any way? Will these be changed together? If not, then it's not a duplication either (the example above is from Mathias's blog). 

Duplication occurs when it is due to business rules and when the rules will change together. It applies to situations where it would be easy to forget to make a change in two, three, or more places. Such duplication should be removed - the simplest example can be e.g. checking the date in different places in the system: 

if ($date->diff(new DateTime())->y < 18) {
    throw new NotAdultException();
}

We can remove this duplication by creating an Adult object that will validate the age of the user or a wrapper for a DateTime object that will control it. 

I would avoid creating a Utils class. Rarely do such classes make sense. However, this is material for a separate article.