Object Oriented Programming
Object orient programming is a programming paradigm that organizes code into objects that combines data and behavior in a single unit. Commonly a unit of data stored in an object is called attribute and the a unit of behavior is called method. Object oriented programming is one of the most popular programming paradigms in the world. Most modern programming languages support object oriented programming today.
Why we need to combine data and behavior in a single unit? Lets examine a simple counter example. Suppose that we want to write a program that counts the number of times a button is clicked. We want to print the number to the console when a user clicks a button. We can write a simple program like this:
// Create a simple variable to store the count
var count = 0;
// Create a function to increment the count
function inc() {
// Increment the counter
count = count + 1;
// Print the counter to the console
console.log(count);
}
// Create a button element
// If you do not know javascript, just know that this block of triggers
// the inc function when the button is clicked
var button = document.createElement('button');
button.textContent = 'Click me';
button.onclick = () => inc();
document.body.appendChild(button);
This program will work as expected. However there are some problems with this program. The first one is that the
count
variable is a global variable. This means that any part of the program can change the value of the count
variable. This can lead to bugs that are hard to find. The second one is related to creating multiple counters.
If we want to create multiple counters, we need to create multiple count
variables and inc
functions. This
can lead to code duplication and hard to maintain code.
// Create a simple variable to store the count1
var count1 = 0;
// Create a function to increment the count1
function inc1() {
// Increment the counter
count1 = count1 + 1;
// Print the counter to the console
console.log(count1);
}
// Create a button element
// If you do not know javascript, just know that this block of triggers
// the inc1 function when the button is clicked
var button1 = document.createElement('button');
button1.textContent = 'Click me';
button1.onclick = () => inc1();
document.body.appendChild(button1);
// Create a simple variable to store the count2
var count2 = 0;
// Create a function to increment the count2
function inc2() {
// Increment the counter
count2 = count2 + 1;
// Print the counter to the console
console.log(count2);
}
// Create a button element
// If you do not know javascript, just know that this block of triggers
// the inc2 function when the button is clicked
var button2 = document.createElement('button');
button2.textContent = 'Click me';
button2.onclick = () => inc2();
document.body.appendChild(button2);
This code is too dirty and hard to maintain. Object oriented programming solves these problems by introducing a method to combine data and behavior in a single unit. This unit is called an object. Lets rewrite the counter example using objects.
// Create counter1 object
var counter1 = {
count: 0, // <-- Attribute
inc: function () {
counter1.count = counter1.count + 1;
console.log(counter1.count);
}, // <-- Method
};
// Create counter2 object
var counter2 = {
count: 0, // <-- Attribute
inc: function () {
counter1.count = counter1.count + 1;
console.log(counter1.count);
}, // <-- Method
};
// Create a button element
var button1 = document.createElement('button');
button1.textContent = 'Click me';
button1.onclick = () => counter1.inc();
document.body.appendChild(button1);
// Create a button element
var button2 = document.createElement('button');
button2.textContent = 'Click me';
button2.onclick = () => counter2.inc();
document.body.appendChild(button2);
This code is much cleaner compared to previos one. However as you can notice, the inc
method contains the same code
except the object name. In order to remove code duplication, we can receive the object the we interested in as a parameter
to the inc
method.
// Create counter1 object
var counter1 = {
count: 0, // <-- Attribute
};
// Create counter2 object
var counter2 = {
count: 0, // <-- Attribute
};
function inc(counter) {
// <-- Receive the object as a parameter (again it is a method)
counter.count = counter.count + 1;
console.log(counter.count);
}
// Create a button element
var button1 = document.createElement('button');
button1.textContent = 'Click me';
button1.onclick = () => inc(counter1); // <-- Pass counter1 object to the inc function
document.body.appendChild(button1);
// Create a button element
var button2 = document.createElement('button');
button2.textContent = 'Click me';
button2.onclick = () => inc(counter2); // <-- Pass counter2 object to the inc function
document.body.appendChild(button2);
By passing the object to the inc
method, we removed the code duplication. This is the basic idea of object oriented
programming. Instead of passing the object that we interested in as an explicit parameter, we can use a special keyword
called this
which is passed to the function implicitly. Lets rewrite the counter example using this
keyword.
var counter1 = {
count: 0,
inc: function () {
this.count = this.count + 1;
console.log(this.count);
},
};
var counter2 = {
count: 0,
inc: function () {
this.count = this.count + 1;
console.log(this.count);
},
};
var button1 = document.createElement('button');
button1.textContent = 'Click me';
button1.onclick = () => counter1.inc(); // <-- No need to pass the object explicitly
document.body.appendChild(button1);
var button2 = document.createElement('button');
button2.textContent = 'Click me';
button2.onclick = () => counter2.inc(); // <-- No need to pass the object explicitly
document.body.appendChild(button2);
However instead of specifying each attribute's initial value one by one we can use a function that will create the object for us. This function is called a constructor. Lets rewrite the counter example using a constructor.
function Counter() {
// <-- Constructor function
return {
count: 0,
inc: function () {
this.count = this.count + 1;
console.log(this.count);
},
};
}
var counter1 = Counter(); // <-- Create counter1 object
var counter2 = Counter(); // <-- Create counter2 object
var button1 = document.createElement('button');
button1.textContent = 'Click me';
button1.onclick = () => counter1.inc();
document.body.appendChild(button1);
var button2 = document.createElement('button');
button2.textContent = 'Click me';
button2.onclick = () => counter2.inc();
document.body.appendChild(button2);
This example shows the basic idea of object oriented programming and covers attribute
, method
, and constructor
concepts. Although there are some improvements that can be made, this example is enough to understand the basic idea
of object oriented programming and we will cover these improvements in the following sections.
Classes
A class is a blueprint for an object. It defines common attributes and methods that objects created from the class will have. In the previous examples, we created objects using object literals and constructor functions. However modern programming languages provide a special syntax to define classes. Lets rewrite the counter example using classes.
class Counter {
constructor() {
this.count = 0;
}
inc() {
this.count = this.count + 1;
console.log(this.count);
}
}
var counter1 = new Counter();
var counter2 = new Counter();
A class is a type in a typed programming language. This means that we can create a variable that has a type of a class. In the previous examples, we created objects using object literals and constructor functions. Lets examine an example for Java programming language.
class Counter {
int count = 0;
void inc() {
count = count + 1;
System.out.println(count);
}
}
public class Main {
public static void main(String[] args) {
Counter counter1 = new Counter(); // <-- Type of counter1 is Counter
Counter counter2 = new Counter(); // <-- Type of counter2 is Counter
}
}
Therefore, you should notice that a class is not a concrete object. It is just a blueprint for an object. When you create
an instance of a class, it becomes an object. In the previous examples, Counter
is a class and counter1
and counter2
are objects.
Encapsulation
Encapsulation is a concept that restricts direct access to some of the object's attrubutes and methods. Encapsulation is used to protect the object's state from being modified by other objects. In object oriented programming, we can restrict access to an object's attributes and methods by using access modifiers.
For instance, in our counter example, the count
attribute is public which means that any part of the program can
change the value of the count
attribute. This can lead to bugs that are hard to find. We can restrict access to the
count
attribute by using access modifiers. Access modifiers are language specific and they can be different from
language to language. Lets examine an example for Typescript programming language.
class Counter {
private count = 0; // <-- Private access modifier
private printToConsole() {
// <-- Private access modifier
console.log(this.count);
}
inc() {
// <-- Public access modifier by default
this.count = this.count + 1; // <-- Access to the private attribute
this.printToConsole(); // <-- Access to the private method
}
}
var counter1 = new Counter();
var counter2 = new Counter();
counter1.inc(); // <-- Access to the public method
counter2.inc(); // <-- Access to the public method
In this example, the count
attribute is private which means that only the methods of the Counter
class can access
the count
attribute directly. The printToConsole
method is also private which means that only the methods of the
Counter
class can access the printToConsole
method directly. The inc
method is public by default which means
that any part of the program can access the inc
method.
Inheritance
Inheritance is a concept that allows a class to inherit attributes and methods from another class. The class that inherits attributes and methods is called a subclass and the class that provides attributes and methods is called a superclass. Lets examine an example for Typescript programming language.
class Counter {
private count = 0;
private printToConsole() {
console.log(this.count);
}
inc() {
this.count = this.count + 1;
this.printToConsole();
}
}
class ResettableCounter extends Counter {
reset() {
this.count = 0;
this.printToConsole();
}
}
var counter1 = new ResettableCounter();
var counter2 = new Counter();
counter1.inc(); // <-- Access to the public method of the Counter class
counter1.reset(); // <-- Access to the public method of the ResettableCounter class
counter2.inc(); // <-- Access to the public method of the Counter class
In this example, the ResettableCounter
class is a subclass of the Counter
class. The ResettableCounter
class
inherits the count
attribute and the printToConsole
method from the Counter
class. The ResettableCounter
class
also has a reset
method that is not available in the Counter
class. The ResettableCounter
class can access the
count
attribute and the printToConsole
method directly. The ResettableCounter
class can also access the inc
method of the Counter
class.
Also, it is possible to override the methods of the superclass in the subclass. Lets examine an example for Typescript programming language.
class Counter {
private count = 0;
private printToConsole() {
console.log(this.count);
}
inc() {
this.count = this.count + 1;
this.printToConsole();
}
}
class ResettableCounter extends Counter {
private printToConsole() {
console.log('ResettableCounter printToConsole method');
console.log(this.count);
}
reset() {
this.count = 0;
this.printToConsole();
}
}
var counter1 = new ResettableCounter();
var counter2 = new Counter();
counter1.inc();
counter1.reset();
counter1.inc();
counter2.inc();
Therefore, by using inheritance, we can remove code duplication and create a hierarchy of classes.
Polymorphism
Work in Progress
Abstraction
Abstraction is a concept that hides the implementation details of an object and only shows the necessary details to the outside world. Abstraction is used to simplify the complexity of an object. In object oriented programming, we can use abstract classes and interfaces to achieve abstraction.
An interface is a specification of a class. It defines a set of methods that a class must implement. Lets examine an example for Typescript programming language.
interface Counter {
count: number;
inc(): void;
}
class InMemoryCounter implements Counter {
count = 0;
inc() {
this.count = this.count + 1;
console.log(this.count);
}
}
class LocalStorageCounter implements Counter {
count = 0;
constructor() {
this.count = parseInt(localStorage.getItem('count') || '0');
}
inc() {
this.count = this.count + 1;
localStorage.setItem('count', this.count.toString());
console.log(this.count);
}
}
// Gets an instance of the Counter interface and increments the counter n times
function countNTimes(counter: Counter, n: number) {
for (var i = 0; i < n; i++) {
counter.inc(); // <-- We do not know which concrete class will be used here
}
}
var counter1 = new InMemoryCounter();
var counter2 = new LocalStorageCounter();
counter1.inc();
counter2.inc();
// Refresh the page and run the program again
When you refresh the page and run the program again, you will see that the count
attribute of the LocalStorageCounter
class is persisted in the local storage. This is an example of abstraction. The countNTimes
function does not know
which concrete class will be used. It only knows that the class will have a count
attribute and an inc
method.
It does not care about the implementation details of the class.
This way of calling a method without knowing the implementation details is called dynamic dispatch. Dynamic dispatch
is a mechanism that determines which method to call at runtime. In the previous example, the countNTimes
function
uses dynamic dispatch to call the inc
method of the Counter
interface. On the other hand, counter1.inc()
and
counter2.inc()
calls the inc
method of the InMemoryCounter
and LocalStorageCounter
classes respectively. This
is called static dispatch. Static dispatch is a mechanism that determines which method to call at compile time.
Prototype Chains (JavaScript Specific, Optional)
Generally, an object oriented programming language has two choices for implementing object oriented programming. The first one is class-based and the second one is prototype-based. In class-based object oriented programming, a class is a blueprint for an object and an object is an instance of a class.
Work in Progress