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

Summary

Exercises

Extra Resources