RU EN

About object-oriented programming

22 April, 2020

Object-oriented programming is believed to be based on three pillars that provide the programmer with advantages over the procedural approach. They are encapsulation, inheritance and polymorphism. Inheritance allows you to significantly get rid of code duplication when using the object-oriented approach. But using it, you should always remember about one of the principles of SOLID, called Liskov Substitution, which, unless you go into details, states that inheritance should be used only as an implementation of the is relationship. That is a child class always should be subspecies of parent class. For example you may have a parent class Bird and subclasses Raven and Sparrow, which may inherit (as well as extend or override) fly method. But if you define class Penguin, which fly method throws exception (due to penguins don't fly) you will violate Liskov Substituition principle. When talking about inheritance, also not to mention another one important, which advises to adhere to the “Gang of Four” (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides) in their book Design Patterns: Elements of Reusable Object-Oriented Software (1994), which states that you should try to use composition (when one object “contains” another object) instead of inheriting as much as possible. Actually, many of the design patterns invented by the “Gang of Four” are precisely based on this principle. In general, in practice, inheritance is not used to say so often, even though in most cases good programmers replace it with composition. More common are multiple implementations of a single interface, which are specified as dependencies in client classes. Which in turn allows you to use the second pillar of OOP - polymorphism. Which allows you to pass different dependency implementations to client code. Due to the fact that the transmitted dependencies have the same interface, the client (depending on the interface) doesn’t care what object came to it and this allows the programmer to change, during the execution of the program, in what way (which implementation of the interface) the final task will be solved. Speaking of OOP, one cannot but mention encapsulation. This is an essential element of OOP that provides the ability to create abstractions in order to hide low-level implementation details, reducing development complexity. Implementation details are hidden by access modifiers to methods such as "private" and "protected". When creating classes, you need to create them so that their interfaces (accessible public methods) provide a consistent abstraction, otherwise this is no longer OOP (!). For example, let's assume that we are developing a program that controls the cooling system of a nuclear reactor. Then the consistent abstraction of the interface can look as follows.


CoolingSystem::getTemperature()
CoolingSystem::SetCirculationRate($rate)
CoolingSystem::OpenValve($valveNumber)
CoolingSystem::CloseValve($valveNumber)
Thanks to a short expressive interface forming a coherent abstraction, we can work with the reactor cooling system without having the slightest idea about the low-level implementation details that are typical for the chosen technology. The use of abstractions can significantly reduce the complexity of the developed system. And the fight against complexity is the main technical imperative of development. Here are some more examples of consistent abstractions:

Speed regulation system:

Сoffee grinder:

Fuel tank:

Lamp:

When designing the interface of an object, declare public only those methods that the client needs for managing, hiding everything else. As a result, you should get black boxes, the device of which you can forget after the class code has been completed. This approach can significantly reduce the complexity of the developed system.