Object-oriented programming

Object-oriented programming

What is object-oriented programming?

Object-oriented programming or “OOP” for short is a way of organizing code around smaller units called “objects”. Object-oriented programming helps structure code into understandable and self-contained units that can be easily reused in different parts of the program. Applying object-oriented programming results in a more transparent and understandable program code that is easy to maintain!

What is an object?

The object in programming has a lot of similarities with the human understanding of the object, and that’s why it got that name. Each object has some of its own properties (properties) and functionality with which it can perform tasks.

An object in programming represents a small part of the program code that stores certain data and has the functionality to perform some previously prepared actions.

Characteristics of the object (“Properties”)

A good candidate for an example object in programming is “Vehicle” (eg a vehicle in a game). A vehicle in real life has a lot of characteristics (‘properties’), but in this example we will mention only a few characteristic ones such as: “vehicle brand”, “engine type”, “year of manufacture”.

Object functionalities (“Methods”)

The vehicle has many functionalities such as: “passenger transport”, “material transport”, “passenger heating”, “lighting”… In order to perform the “passenger transport” functionality, it is necessary to perform a whole series of actions: unlock the door, turn the key, put it in gear, give gas… All the mentioned small actions that precede the fulfillment of the functionality are an excellent example of grouping. A Grouping of programming code that is responsible for executing one functionality of an object is written in an even smaller program unit called a method of the object.

Creating objects (“Classes”)

In ordinary life, in order to build an object, its project or plan is needed on the basis of which we would build it. The same is true in programming, the template for creating an object in programming is called “class”. A class has the same role as a template in real life, it defines what the created object will look like, what properties and what functionalities it will have. An object created from a class (‘template’) is called an “instance” of the class.

All objects created by the same class have the same properties and functionalities but with different values of those properties.

An object created from a class (“template”) to create a vehicle would have the same properties as any other object created from the same class (“make”, “engine type”, “year of manufacture”), but with its own values of those properties. For example a vehicle instance could have the value “Renault 4” for the “make” property, the “engine type” property the value “1.0 gasoline” and the “year of manufacture” property the value “1986”. In the following example, the “Vehicle” class has two properties: “Vehicle Brand” and “Year of Manufacture” and one method “drive()”:



Creating and using an instance of the Vehicle class within a program would look like this (also in the JS and TS version):

In the example “myCar”, the object was created based on the class “Vehicle”, and when the object was created, values were set for the vehicle brand (“Toyota”) and the year of production (“2020”). This object stores those two pieces of information and has the added functionality of printing “2020 Toyota is driving” to the console whenever we call the “drive()” method.

EXPLANATION:
The special JS method “constructor” is called only once when creating a new instance of the class. In our example, the constructor accepts the input of two values ​​and then sets those values ​​to the class properties (“makeVehicle” and “yearProduction”) when instantiating the object itself. See more about classes in the article “Classes in JS”

Private object properties

It is often necessary to deny access to some properties of an object outside the object itself, such properties are called “private” properties and they are not accessible outside the defined object, ie. they are “hidden” from external code.

If we tried to write during programming:

This will cause a JavaScript error (ES2020 and later).

If we tried to write during programming:

The TypeScript compiler will report an error because you are trying to access a private property of a class from outside it.

In the previous example, the “VehicleMake” and “ProductionYear” properties are private, meaning they cannot be accessed or changed directly outside of an instance of the class. The drive method can still access these private properties because it is part of the class. If we tried to access a private property or change its name, the compiler would throw an error.

In JavaScript, the concept of private properties was not formally supported until recently (ES2020 introduced private fields with the # prefix). Before that, the convention was to use certain names (such as the underscore “_” prefix) to signal that certain properties were “private”. However, this was not a true encapsulation at the language level, but more of a convention within the developer community.

Encapsulation

EXPLANATION:
“Encapsulation” is one of the principles of object-oriented programming and is based on hiding the state of a class property to code outside the class itself, allowing controlled access to the property only through methods predefined for it.

In the previous example, it is clear why it is necessary for the “Vehicle brand” and “Production year” properties to be private, i.e. to be set only once when creating an object without methodsto subsequently change outside the class itself. However, sometimes you need private properties whose values ​​can be changed from “external code”. In such cases, it is possible but only through controlled access, an excellent example would be the new private property “body color”, whose change would be allowed outside the object only through a specialized method called “paintingCar()”:



Abstraction

“Abstraction” is another principle of object-oriented programming and involves focusing on what is important and hiding complexity from the user (user = “code outside the class”). When there is such a need for some functionality that we don’t want to be accessed outside the class then we need to define it as a “private function”.

In the following example, we will add a new private property “amountofFuel” whose value can only be increased through the specialized method “fillFuel()”, and we can change the method “drive()” and give it new functionality so that it will be in charge of reducing the amount of fuel in the tank depending on how many kilometers the drive has to cover. From now on, this method will accept one input parameter “number of kilometers driven” and based on it and the value from the property “Fuel consumption” will reduce the amount of fuel in the tank according to the appropriate formula. Also, the method should check whether there is enough fuel for the planned drive and if there is not, then not to drive and to print a warning message in the console. Checking whether there is fuel in the reservoir will be dedicated to a private function called “IsThereEnoughFuel()”. Since this method is only needed inside the class and does not need to be accessed by anyone outside, it is an excellent example of abstraction (hidden complexity) and a private function.



In the Vehicle class, the details of how the fuel consumption is calculated or how to check if there is enough fuel are hidden inside the method, so when the user of the class calls the drive() method he does not need to know the details of how the fuel is consumed or how the fuel level is checked.

Inheritance

Object orientedprogramming allows the subclass to inherit all the characteristics of its base class, but at the same time to add new functionalities. In this example, the “Formula” class inherits all the properties and methods of the “Vehicle” class and extends them with its new “_maxSpeed” property and “driveRace()” method. The driveRace() method is similar to the drive() method, but unlike it, it consumes twice as much fuel during the race as normal consumption (when the drive() method is called).

auto-formula

NOTE:
Within the base class, the method “HasEnoughFuel()” is private (in TS with the keyword private, and in JS with the prefix #). In order to enable the use of the private method changeFuel within the subclass, in TS it is necessary to change its state from “private” to “protected”. However private methods and properties in JavaScript marked with # are only available within the class in which they are defined and there is no direct way to make them available in extended classes (as is possible in TypeScript with protected). In order to do this in JS, instead of real privacy (methods marked with the prefix “#””), we only need to use the naming convention with the prefix “_”, which indicates that the methods are “private”, but they technically remain publicly available.



The new extended class could be used in the program as follows:

Polymorphism

Object-oriented programming supports another concept “Polymorphism” which is the ability of code to behave in different ways, depending on the object it works with. This is especially useful in situations where we want different classes to have the same method name, but each class implements that method in its own way. There are two basic types of polymorphism: static (or compile-time) “Overloading” and dynamic (or run-time) “Override”.

Dynamic Polymorphism (Override)

An excellent example of dynamic Polymorphism would be the method vozi(), where when calling the method we print different messages depending on the class:

And the extended Formula class would now look like this:

Polymorphism demonstration:

The previous example shows Dynamic Polymorphism (Override) where the functionality of the method is “overridden” in the extended class. The compiler knows during the execution of the program (runtime), based on the object type (“Vehicle” or “Formula”), which method should be called!

Static Polymorphism (Overloading)

This type of polymorphism happens when we have multiple methods within the same class with the same name but with different parameters (number, type, etc.). The compiler decides which version of a method to use based on the arguments the method receives when called. In the programming languages “Java” and “C#” real overloading of methods within a class is enabled, which means that within a class you can have multiple methods with the samename, but with different signatures (different types and/or number of parameters). Each of these methods may have its own unique implementation. Here is an example of overloading in C#:

Example

In TypeScript, method overloading is achieved by defining multiple signatures for the same function, but with a single implementation that must be compatible with all signatures. Here is an example of a calculator similar to the C# example, but adapted for TypeScript:

In this TypeScript example, the Calculator class has two signatures for the add method: one with two parameters and one with three parameters. There is only one implementation of the add method that uses an optional parameter (denoted by ?) to allow the method to be called with two or three arguments. In this way, TypeScript allows overloading while preserving the typical static type checking features provided by the language.

Example

In this example, the same method can also receive “different type of parameters”, so in C# it looked like this:

Since TypeScript doesn’t support multiple actual implementations of the same function name (like Java and C# do…), we have to use union types and type checking within a single implementation to handle different cases. Here’s what it might look like:

In this version, the add method can accept either numbers or strings. A function inside the class uses typeof checking to determine whether the arguments are numbers or strings and performs the appropriate operation (adding numbers or concatenating strings) based on that.
This is a flexible way to handle different types of parameters within a single function in TypeScript, although the data types must be handled carefully to avoid runtime errors.