Using Object-Oriented Programming to Solve Problems In JavaScript
Object-oriented (OO) programming is a powerful paradigm that a lot of tutorials and experts suggest using but they never tell you how to apply it to solving real-world problems. This is what this article tries to achieve. You will learn how to visualize the processes that your code will go through and also form a very high-level abstraction of the algorithms you will create. To understand this article you will need to have some knowledge of JavaScript.
Object-Oriented Programming
This is a programming paradigm that focuses on "objects" that the programmer wants to manipulate on the high-level rather than the detailed logic at the lower level. Objects can either contain attributes (the data), methods (the behaviour) or both. A list of the popular programming languages are: Java, C++, C#, Python, R, PHP, JavaScript, Ruby, Perl, Objective-C, Dart, Swift, kotlin and smalltalk. But JavaScript, unlike all the other OOP languages listed here, is not a fully object-oriented language because it doesn't have the concepts of classes, only objects and therefore, it isn't class-based. It is prototype-based, in the sense that it uses a constructor or factory function to create objects and it can use the object's prototype as a template for a new constructor function.
Object-Oriented Thought Process
In the first step of designing an OO program, programmers create a data model which tries to model the real world. The components of the real world are represented as objects that have properties (attributes) and behaviours (methods) which interact with each other to simulate the solution. In this process, the programmer considers;
- What data will be revealed to other objects (abstraction)
- What data do each object contain and how they behave (encapsulation)
- The hierarchy of objects (inheritance), and
- What combination of objects form another object (composition)
Abstraction
Abstraction is a very important concept of object-oriented programming that helps to reduce the complexity of an OO design by only allowing other objects to access the data that is required and hiding all the others. For example, lets say we have a timer object and the data that it contains are...
- total milliseconds counted
- hours
- minutes
- seconds
When we want other objects to use this object, we will only want them to have access hours, minutes and seconds attributes only because the total milliseconds counted property of the object is a low-level detail that will be used by the object to get the number of hours, minutes and seconds. So, we will make total milliseconds counted
private while the others will be public.
Encapsulation
This is a concept in OOP that deals with packaging data and the methods that works with them together in a class. It creates the concept of Internal State
of an object, where only the object has access to the state data. For example, we are writing a code that fetches the latest news from the internet and we are using a class with the following contents:
Data
Private
- connected to the internet: false
- last fetch: 5 mins ago
Public
- type of news to fetch: sports
Methods
Private hidden implementation
Public
- fetch news from the internet
In this class, the internal state variables are connected to the internet
and last fetch
. Because we wouldn't want the user to worry about their code affecting the class and we also wouldn't want to allow other objects to mess with the internal state, this data is kept private to the object and no direct access is allowed outside the object.
Inheritance
Inheritance is another important feature of OO programming that is about basing classes or objects on other classes (for class-based constructor) or objects (prototype-based constructor). It allows a class to derive initial features from another class. It promotes code re-usability, efficiency and reduction of redundancy. In inheritance, parent to sub class relation can be represented as "sub" is a "parent"
.e.g Apple is a Fruit, Car is a Vehicle, Dog is an Animal. Examples of inheritance scenarios are;
- Lets imagine that we just built a simple platform running game and in the pause menu we need a "resume" button, a "restart" button and an "exit" button. To do that we can create one major class "PauseMenuButtons" that holds all the methods that will be common to all the buttons like the buttons event handler reactions. Then we create the "resume", the "restart" and the "exit" buttons that extends (inherits properties from) "PauseMenuButtons".
- We made a game. In this game we will need to have two players, "User" and "CPU". The best way to implement this will be to create a "Player" class that houses all common features of the player ("User") and "CPU". Then have "User" and "CPU" inherit the "Player" class with little modifications made.
Composition
Composition one of the most important concept in OOP that allows building of bigger classes by using smaller objects as building blocks. It is similar to inheritance as they both object mechanism improve code re-usability. In composition, object relations are represented by "has a". That means if A is related to B through composition, A has a B. For example; lets say we have a program that counts down in seconds. For the app we will create a main class called "App". This class will contain timer, start button, stop button and an input field. This objects are representations of what will be included within the class. All the objects that compose the "App" will be connected to the UI (User Interface), the event handlers and the screen display. And that is how composition works.
Composition Over Inheritance
Composition over inheritance is an argument thats been in the OOP community for a very long time. For a lot of reasons some programmers say composition is more preferable to inheritance. There are certainly more areas where composition will be the best suit, but, it doesn't mean that it is the best form of reusability in all scenarios. Some of the reasons supporting composition are;
- It allows a model to be modified by simply adding objects of the desired functionality to the class without affecting the logic.
- It is simplier to model real-world problems using composition to break it down into what the objects contain, instead of struggling to find relations between classes and forming a hierarchical tree by inheritance.
- Modelling complex problems using composition is so much simpler than inheritance
- Changes can be made to the dependency at runtime, but once a class inherits a property it stays with those properties until deletion of the object.
Using OOP In JavaScript
There are several different ways to create objects using JavaScript and they are:
Constructor Function
A simple example of a constructor function is;
function Car(color) {
this.color = color;
let workTheEngine = function() {
// this function is not revealed to the user
}
this.drive = function() {
workTheEngine();
}
}
To create an object using this type of function, you put a new
before the function call. Example;
let redCar = new Car("red");
In this object, only the color
property and the drive
function are public. The workTheEngine
is not public because it is not added to the this
object. In JavaScript, this
is a reference to the object's public state. That means, any member added to the this
object will be public to all the objects.
Factory Function
The previous example's factory function version is;
function Car(color) {
let workTheEngine = function() {
// this function is not revealed to the user
}
return {
color: color,
drive: function() {
workTheEngine();
}
};
}
This function directly returns a JavaScript object so there wouldn't be any need to use new
when creating a new object;
let redCar = Car("red");
This object is just like the first, only the properties and methods that are in the returned object are revealed to the user. And the returned objects access the workTheEngine
function through a concept called closure. A closure is a concept of programming where returned functions or object access the local binding of the function that returned it.
Class Notation
In JavaScript, the class notation is a syntactic sugar for the constructor function. It is there to make programmers coming from other OO languages like C++, Java, etc. feel more comfortable using OOP in JavaScript. And because JavaScript is not a class-based language, features like abstraction (data hiding) is not possible using the class notation. The class notation version of the car example is;
class Car {
constructor (color) {
this.color = color;
}
workTheEngine (color) {
// some hidden implementation that is not meant to be shown the user
}
drive () {
this.workTheEngine();
}
}
And it is used the same way that a constructor function is used (with a new
before the function call). In the JavaSript class notation it is not possible hide data or methods from the user and it can lead to quick over-complication of the code. Because of this, it is advised that you use either the constructor function or the factory function if you are not new to JavaScript. An advantage of the class-notation is the ability to extend the object. For example; imagine we want to create two more types of objects: a 4x4 and a cruiser, to prevent writing the same code over and over again we will create those two classes and have them inherit properties from the Car
class;
class Cruiser extends Car {
// methods and properties specific to the cruiser
}
class Car4x4 extends Car {
// methods and properties specific to the 4x4
}
Prototype-based Constructor
All class-notation objects are converted to the prototype-based version before runtime. The prototype-based constructor function works just like the normal constructor function but it add features like inheritance to it. A simple example the prototype-based constructor is;
function Car (color) {
this.color = color;
}
Car.prototype.workTheEngine = function (color) {
// some hidden implementation that is not meant to be shown the user
}
Car.prototype.drive = function () {
this.workTheEngine();
}
In this construct too, It is not possible to create private methods or properties. Because, there is no way the that the fields can interact with each other if they are not public for all to see. To use inheritance, we first assign the parent's prototype to the child's prototype and then we start to add new features to the child. For example;
function CruiseCar (color) {}
CruiseCar.prototype = Car.prototype;
CruiseCar.prototype.thisCarSpecificBehaviour = function () {
// behave a certian way
}
function Car4x4 (color) {}
Car4x4.prototype = Car.prototype;
Car4x4.prototype.thisCarSpecificBehaviour = function () {
// behave a certian way
}
Conclusion
Object-oriented programming is a powerful and effective programming paradigm and it pays to learn it as it promotes creativity due to the fact that everything it deals with are imaginable objects.
Thanks for reading and have a good day!