Object Oriented Programming: The four pillars of OOP

There are many concepts in object-oriented programming, such as classes and objects, however, in the development of software with object-oriented programming, there is a set of fundamental ideas that form the foundations of software development. These 4 concepts that we are going to see we call them the 4 pillars of the object oriented programming.

This does not mean that outside of these 4 pillars there are no other equally important ideas, however, these 4 pillars represent the basis of more advanced ideas, so it is crucial to understand them.

These pillars are: abstraction, encapsulation, inheritance and polymorphism.

Note: Just as a reference, here’s the class we did in the previous post:


public class Car
{
  public string Brand;
  public int ModelYear{ get; set; }
  public void Accelerate()
  {
  }
}

Abstraction

According to a dictionary, one of the meanings of abstraction is:

Ignore something, or set it aside.” And then it offers as an example: “Let’s focus on the essential by abstracting from marginal considerations.”

The example given captures the essence of the concept of abstraction. When we make an abstraction, we want to omit details that are not necessary for us, and we only want to show what is relevant.

From the point of view of software development, we can see that with a class we can make an abstraction of a real world entity. Take for example the car class that we made, this has the possibility of saving data related to the brand and the year of the car, but, why only these two pieces of information? A real world car has more properties, such as color and model. However, we must ask ourselves, are these information relevant to our software?

Our class abstracts everything that a car represents, taking only what interests us, discarding everything else.

Encapsulation

You already know that you can use classes to model entities which are relevant to your application, you also know that you can save data within objects, and also execute functionality. The question we must ask ourselves is, should anyone be able to directly modify this data? Should anyone execute any functionality of our objects at any time? Normally this is not something we want, we want to be able to control the way in which the data is assigned, we want to be able to control who sees the internal data of our objects, and maybe we even want to control the execution of functionality of our objects. For this we have the concept of encapsulation.

The encapsulation allows us to control who can see and use the different internal modules of our system. In terms of classes, with encapsulation we define access to class members.

In C # we can use access modifiers to define the control of external agents to different parts of our system, such as classes, members of classes, interfaces, among others. Suppose we have a variable, called velocity, which we want to place in our Car class, to indicate the speed at which a particular vehicle is traveling. However, we want that only within the class we can see and modify the value of said variable. We can do this either with a field or with a property. Let’s do it with a property:


public class Car
{
  public string Brand;
  public int ModelYear { get; set; }
  private int Velocity { get; set; }
  public void Accelerate()
   {
    Velocity += 10;
   }
}

When we make an instance of the Car class, we can not access the value of the Speed property, nor can we alter it from the outside. What we can do is use the Accelerate function to increase the velocity value by 10 units. This is one of the advantages of encapsulation: It allows us to control the way in which the internal data of our object is going to be altered.

If we want external agents to be able to see the value of the Velocity property, but can not freely alter this value, we can use the following syntax:


public int Velocity { get; private set; }

Inheritance

Sharing code is an important and crucial feature of any software project. Sharing code allows you to save work when you want to make a change in your system; allows a single algorithm to process different kinds of entities; among other things.

There are several ways to share code, one of them is using inheritance. Inheritance is a special relationship between two classes, the base class and the derived class, where the derived class obtains the ability to use certain properties and functionalities of the base class, even replacing functionality of the base class. The idea is that the derived class “inherits” some of the characteristics of the base class.

We can see an example with the Car class. A car is a type of vehicle, in addition, we want to process other types of vehicles, each with its own entity, such as a truck. A car and a truck share the concept of velocity, in addition, both have the ability to accelerate, and both have the ability to go backwards, however, when a truck goes in reverse, it must emit a sound. Finally, a car must be able to turn on the radio. Let’s model this:


public class Vehicle
{
  public string Brand;
  public int ModelYear { get; set; }
  public int Velocity { get; private set; }
  public void Acelerate()
  {
     Velocity += 10;
  }

  public virtual void Reverse()
  {
    Console.WriteLine("Going in reverse!");
  }
}

public class Car: Vehicle
{
  public void TurnOnRadio()
  {
    Console.WriteLine("Turning on the music");
  }
}

public class Truck: Vehicle
{
  public override void Reverse()
  {
    base.Reverse();
    Console.WriteLine("BEEP BEEP BEEP!");
  }
}

We see that we have 3 classes: Vehicle, Car and Truck. Car and Truck inherit from the Vehicle class. The inheritance relationship is represented in this way:


class Car: Vehicle

With this syntax we say that Car is a class derived from Vehicle.

We also see that the Accelerate function is defined in the Vehicle class, this means that all derived classes can use this function. The same happens with the fields and properties.

Certainly the Car and Truck classes can define their own members that are not related to the Vehicle class. For example, the Car class has the TurnOnRadio method.

We can also modify functionality of the base class. For this, in the base class, the method must be marked as virtual. And when you want to override, that is, change or add functionality, this can be done by doing an override, as we see in the Truck class. Within the Reverse method of the Truck class, we have the base.Reverse(); code, which serves to invoke the reverse method of the base class, and then it adds its own functionality.

We can use the previous code in the following way:


Car myCar = new Car();

myCar.ModelYear = 2018;

myCar.Accelerate();

Console.WriteLine(myCar.Velocity);

myCar.Reverse();

Console.WriteLine("-------");

Truck myTruck = new Truck();

myTruck.Accelerate();

myTruck.ModelYear = 2012;

myTruck.Reverse();

Abstract Classes

What if we wanted the Vehicle class to not be instantiated? We can mark it as an abstract class. An abstract class is one that can not be instantiated. It is useful in inheritance situations where we do not want users to instantiate the base class, but we want them to only instantiate the derived classes. To mark the class Vehicle as abstract we use the abstract keyword:


public abstract class Vehicle

What if we wanted to force the derived classes to implement a specific function, without the base class giving a default implementation? For this we can mark the method as abstract. Example:


public abstract void ObligatoryToImplement();

Interfaces

The interfaces help us to perform another type of inheritance. While a base class offers us the default implementation of some methods, such as the reverse method of the Vehicle class, the interfaces offer us a set of members that the classes that implement the interface must implement. Interfaces can not be instantiated, just like abstract classes.

Historically, a fundamental difference between interfaces and abstract classes is that abstract classes allow us to create default implementations of methods, interfaces can not do this. However, it is possible that in C# 8 that will change with the introduction of default implementations in interfaces.

Note: Although interfaces are a type of inheritance, it is normal to refer to inheritance only to the case in which we have a base class.

Polymorphism

When we started talking about inheritance, we said that inheritance “allows a single algorithm to process different kinds of entities”. The idea is that we can have a function which receives a parameter, such as a base class, and we can pass to that method objects that are instances of the classes derived from that base class. The same happens if the method receives an interface as a parameter. We can pass to this method any class that implements said interface.

Polymorphism means many form. In our case we call polymorphism when a method receives a parameter that handles several types.

Let’s see an example of polymorphism where we pass to a method the base class Vehicle:


static void Repair(Vehicle vehicle)
{
  Console.WriteLine("Starting repairing");
  Console.WriteLine("Testing accelerate");
  Console.WriteLine($"Initial velocity {vehículo.Velocity}");

vehicle.Accelerate();
  Console.WriteLine($"Final velocity {vehículo.Velocidad}");
  Console.WriteLine("Testing reverse");

  vehicle.Reverse();
  Console.WriteLine("Done!");
}

This method invokes the Accelerate and Reverse methods of the vehicle that is sent as a parameter. The advantage that this offers is that we can generalize algorithms so that they work with different types. In this case, this method works with any class that inherits from Vehicle, in this sense, even if in the future we add the class Motorcycle, which inherits from Vehicle, we can use this new class with the Repair method, and it will work perfectly. In this way the polymorphism occurs, since the repair method can work with several different types.

In the repair method, we can not use the TurnOnRario method of the Car class, because the Vehicle class does not implement this method. What we could do is use the is operator to cast to Car in case the Vehicle is a car:


if (vehicle is Car myCar)
{
  myCar.TurnOnRadio();
}

The previous syntax is equivalent to:


if (vehicle is Car)
{
  Car myCar = vehicle as Car;
  myCar.TurnOnRadio();
}

Conclusion

In this post we saw the 4 pillars of object-oriented programming: Abstraction, encapsulation, inheritance and polymorphism. We also talked about abstract classes and interfaces.

Leave a comment