Object oriented programming and its 4 pillars
What is OOP ?
From the good old days of procedural programming, we all know how to represent a state in terms of structures and an action in terms of procedures. So what is a state and what is an action?
- state: knowledge of something, stored information, characteristics of an entity
- action: processing this knowledge/information, doing something with it, working with an entity
In programming, we need bytes to store the knowledge and instructions to describe the flow of processing it. Object-Oriented programming reexamines these two aspects of coding. Real-world entities require or allow specific actions. Real world operations are related to the entities they operate on. OOP gives us exactly this. It packs state and procedures in a single entity – object. Thinking this way makes it easier and intuitive to describe the real world in code. The state represents an object’s characteristics and procedures represent an object’s behavior. We call them properties and methods. Object-oriented programing is a paradigm defining rules which facilitate modeling of the real world using programming languages. Describing an object’s structure and behavior is done in classes. A class defines how an object should look like and how it behaves. It is like a formula, a schema, a blueprint used to mold a specific object. An object is made from the class definition and is populated with concrete data. It has an identity and is distinguishable from other objects forged from the same class. It is an instance of the class. Just like a car has an engine which can be started, a tank which can be fueled, a transmission box which can change gears and so on. But every car has its own engine with its own VIN (vehicle identification number). And all cars support the same behavior (at least a common set of operations).
What are the 4 pillars of OOP ?
Let’s assume we have two classes – a car and a motorcycle. They both have engines. They both can turn and be fueled. But they use different mechanisms. One uses a steering wheel the other uses a rudder. Their tanks are of different size. We can say that they are concrete examples of a vehicle. It has an engine, it can turn, it can be fueled. But how exactly? It depends on the concrete class of vehicle. So the vehicle is an abstraction. Abstraction is of such importance to OOP that it is actually reflected in most common languages’ syntax. Abstraction is the process of picking common features out from detailed classes to a less specific parent abstract class. If I had to provide one synonym for ‘abstraction’ it will be ‘generalization’. Taking common features from the intersection of some classes and extracting them out is a main characteristic of the abstraction. Abstraction does not allow identity. Abstract classes can not be instantiated. You can not have an abstract object. Can you drive a vehicle which is not a car, nor a motorcycle or any other concrete implementation? But if all vehicles are able to turn using their own mechanism we can tell a vehicle to turn. Abstraction decouples usage from working representations by reorganizing common behaviour.
Hiding the object’s state and characteristics. Or filter the access to them. Do not expose the internals of the object. Usually, drivers do not care about pistons, ignition and so on. They just want to start the engine. Encapsulation protects the users from the complexity of how some publicly available service is working. It leverages the design. Objects can interact with each other using only the non-encapsulated methods – the public API. However, sometimes in order for some object to be useful, we need a way to modify its state. This is where getters and setters play their role. The setter is a method which accepts a value as a parameter and knows how to apply that value to modify the object’s state. It is responsible to verify that the value is from the acceptable definition set allowed for this object’s property. Or it may compute the value of the property from the input parameter. The setter provides input validation. The getter on the other side is responsible for output sanitization. Getting an object’s property may require some filtering of what gets exposed. Or may require copying the property’s value if the object is meant to be immutable and no direct access to its properties is allowed. For many people encapsulation summarizes to data access modifiers(public, private, protected,package-private), but closures are also a type of encapsulation. Hiding data from being modified in a way not intended by the developer, or totally restricting access to a data used for internal implementation purposes or exposing variable in a non-modifiable way is also an encapsulation.
Creating classes that are built upon existing classes. Reuse code with an option to add state or behaviour or override existing behaviour. Share behaviour definition or even implementation by deriving it from a base entity(class, prototype, etc.). An F1Bolid class may inherit its basic car availabilities from the Car class but also adds different equipment appropriate for racing. At the end of the day, F1Bolid is a car which is a vehicle. Most often inheritance is described simply by the “is-a” relationship. However, for the most popular OOP languages, this holds true. The most common way to provide inheritance is through subtyping and subtyping establishes the “is-a” relationship. However, inheritance and subtyping are not exactly the same thing. Subtyping is an instrument to apply inheritance. But the inheritance is mostly implementation reuse.
The exact behaviour is determined by the identity (type). From ancient Greek ‘poly’ means ‘many’ and ‘morphic’ means ‘shape’. In other words, one method can take many shapes. What is the exact behaviour a method represents depends on the essence of the object it is called on. For example, both the car and the motorcycle can turn. But one is going to rotate the two wheels on the front axis and the other is going to rotate the rudder. It should be noted that polymorphism does not require inheritance to work. There are languages in which interface implementation is implicit. If a class implements the methods with specific names, number/type of arguments and return types it is said to implement the interface defining these method signatures. There is no subtyping. Polymorphism allows us to treat different classes of objects in a common way and let any of the specific objects define how to implement the behaviour requested from them. What exact ‘shape’ this behaviour takes depends on the object itself and its implementation.