Skip to content

Instantly share code, notes, and snippets.

@fnurhidayat
Created July 14, 2020 02:38
Show Gist options
  • Save fnurhidayat/253ea8d49a9b990bacb51ef842579862 to your computer and use it in GitHub Desktop.
Save fnurhidayat/253ea8d49a9b990bacb51ef842579862 to your computer and use it in GitHub Desktop.

Overview

Object Oriented Programming is an paradigm of programming, in a nutshell, it is a style to code. Just like a poem, you have a style on making it.

Goals

  • You know what OOP is.
  • You know what class declaration is.
  • You know all of the OOP's Pillar.
  • You understand about the abstraction concept.
  • You understand about the encapsulation concept.
  • You understand about the inheritance concept.
  • You understand about the polymorphism concept.

Table of Content

Preliminary

Before you learn about OOP, you need to know about the Class is. To declare a class, you need to use the class keyword.

class Person {
  
  constructor(name, address) {
    this.name = name;
    this.address = address;
  }

}

Those code above is a class declaration. We have a class called Person.

Class is basically a blueprint of an object. Let's say you want to have 3 objects that contains name and address.

let person1 = {
  name: "Fikri",
  address: "Solo"
}

let person2 = {
  name: "Fikri 2",
  address: "Solo"
}

let person3 = {
  name: "Fikri 2",
  address: "Solo"
}

What if you want to add another property let's say isMarried? You have to write that thing one by one.

So, this is where the class come handy.

class Person {
  
  constructor(name, address, isMarried) {
    this.name = name;
    this.address = address;
    this.isMarried = isMarried;
  }

}

let person1 = new Person("Fikri", "Solo", false);
/* This will create an object like this 
   {
     name: "Fikri",
     address: "Solo",
     isMarried: false
   }
  */

let person2 = new Person("Fikri 2", "Solo", false);
let person3 = new Person("Fikri 3", "Solo", false);

Constructor

Object has property, to define the property of the object through Class Declaration, we need something called constructor method.

class Person {
  
  constructor(name, address) {
    this.name = name; // This will create a property called name
    this.address = address; // This will create a property called address
    this.isMarried = false; // This will create a property called isMarried
  }

}

let fikri = new Person("Fikri", "Solo")
/*
  Notice that when you write new Person() it is actually calling the constructor Method, method == function.

  In constructor method, it requires 2 params, name and address, so when you write new Person, it is expecting you to pass the argument for that method.
  */

console.log(fikri);
// Output: Person { name: "Fikri", address: "Solo", isMarried: false }

Property

Property is just the plain object, it's a data of an object.

// Plain object

let person = {
  name: "Fikri",
  address: "Solo"
}

console.log(person.name);
// Output: Fikri

// Using class
let personWithClass = new Person("Fikri", "Solo")
console.log(personWithClass.name);
// Output: Fikri

But, in the object with Class, there are two types of property:

  • Instance (Or it is called prototype in Javascript)
  • Static

Instance Property

Instance property is a property that we can access after the instantiation of an object. In a nutshell, it can be called once you've run the new Person statement. Or in real life example, instance properties are name, age, address, etc. Human is a class, and Michael Jackson is the instance of Human.

// Declaration
class Human {
  
  constructor(name, isAlive) {
    this.name = name; // This will create a property called name
    this.isAlive = isAlive; //
  }

}

// Instance 1
let mj = new Human("Michael Jackson", false) // Instantiation of Human class. It means we create a new object on a certain class.

// Instance 2
let km = new Human("Karl Marx", false) // Instantiation of Human class. It means we create a new object on a certain class.

console.log(mj.name) // mj.name is an instance property of an instance from Human called mj.
console.log(km.name) // km.name is an instance property of an instance from Human called km.

Instance is just like doing a gossip, when we talk about a person.

Static Property

Static property is a property that the class itself has. It is like talking about Human in general.

class Human {

  static isLivingOnEarth = true;
  static group = "Vertebrate"; 
  
  constructor(name, isAlive) {
    this.name = name; // This will create a property called name
    this.isAlive = isAlive; //
  }

}

console.log(Human.group) // Output: Vertebrate
console.log(Human.isLivingOnEarth) // Output: true

We don't need to instantiate an object when we call a static property.

Method

Method is a behaviour of an object or a class. Just like human, we have such of behaviour like Breathing, Walking, Talking, etc. There are also two types of Methos:

  • Instance (Or Prototype)
  • Static

Instance Method

Just like the instance property, we can only call the instance method once we've instantiate an object of class.

class Human {

  static isLivingOnEarth = true;
  static group = "Vertebrate"; 
  
  constructor(name, isAlive) {
    this.name = name; // This will create a property called name
    this.isAlive = isAlive; //
  }

  // Instance method signature
  introduce() {
    console.log(`Hi, my name is ${this.name} and I'm ${this.isAlive ? "alive" : "dead"}`);

    /*
      `this` keyword refers to the instance object.
      In this case we have an instance named `mj`,
      so it will return this thing
      Person { name: "Michael Jackson", isAlive: false } 

      Which we can also call another method
      */
  }

}

// Instantiate
let mj = new Human("Michael Jackson", false);
mj.introduce() // Output: Hi, my name is Michael Jackson and I'm dead

Call another method inside the method signature

Keep in mind, that a method or function shouldn't have more than 30 lines of code, when your function/method reach 10 lines of code, you should have asked yourself, is it necessary to write it 10 lines of code, or I can just split that into another function/method that I will call inside this function/method

class Human {

  static isLivingOnEarth = true;
  static group = "Vertebrate"; 
  
  constructor(name, isAlive) {
    this.name = name; // This will create a property called name
    this.isAlive = isAlive; //
  }

  // Instance method signature
  introduce() {
    console.log(`Hi, my name is ${this.name} and I'm ${this.isAlive ? "alive" : "dead"}`);
  }

  // Check if it can work
  checkWorkCapability() {
    if (!this.isAlive) {
      console.log("I'm dead, I obviously can't work!");
      return false;
    }
  }

  // Prepare for work
  prepareForWork() {
    console.log("Take a bath");
    console.log("Suit up!");
    console.log("On the way to the workplace");
  }

  doWork() {
    console.log("Coding");
    console.log("Get Stuck");
    console.log("Opened up Stack Overflow");
  }

  work() {
    if (!this.checkWorkCapability()) return; // Stop if he/she can't work
    this.prepareForWork();
    this.doWork();
  }

}

let mj = new Human("Michael Jackson", false);
mj.work() // Output: I'm dead, I obviously can't work!

let frn = new Human("Fikri Rahmat Nurhidayat", true);
frn.work() // Print out a lot of text

Static Method

Static method is just like the static property.

class Human {

  static isLivingOnEarth = true;
  static group = "Vertebrate"; 
  
  constructor(name, isAlive) {
    this.name = name; // This will create a property called name
    this.isAlive = isAlive; //
  }

  // Instance method signature
  introduce() {
    console.log(`Hi, my name is ${this.name} and I'm ${this.isAlive ? "alive" : "dead"}`);
  }

  static isEating(food) {
    let foods = ["plant", "animal"];
    return foods.includes(food.toLowerCase());
  }

}

console.log(Human.isEating("Plant")) // true
console.log(Human.isEating("Human")) // false

Check the instance's class of an object.

We can check an object is an instance of a class by using the instanceof method

let mj = new Human("Michael Jackson", false);
console.log(mj instanceof Human) // true

Modifying Class Method/Property on fly

We can add more method or even modify method on fly, outside the class declaration.

class Human {
  
  constructor(name, address) {
    this.name = name;
    this.address = address;
  }

  introduce() {
    console.log(`Hi, my name is ${this.name}`)
  }
}

// Add prototype/instance method
Human.protoype.greet = function(name) {
  console.log(`Hi, ${name}, I'm ${this.name}`)
}

// Add static method
Human.destroy = function(thing) {
  console.log(`Human is destroying ${thing}`)
}

let mj = new Person("Michael Jackson", "Isekai");
mj.greet("Donald Trump"); // Hi, Donald Trump, I'm Michael Jackson

Human.destroy("Amazon Forest") // Human is destroying Amazon Forest

Note: You can only use the function keywords, if you use arrow function, it won't work.

Four Pillars of OOP

There are 4 pillars in OOP, pillar means concept.

Inheritance

Inheritance is basically just copying the same object and add more feature in it. Just like a family, there's parent and child, where the children will inherit every behaviour and property from their parents.

class Human {
  
  constructor(name, address) {
    this.name = name;
    this.address = address;
  }

  introduce() {
    console.log(`Hi, my name is ${this.name}`)
  }

  work() {
    console.log("Work!")
  }
}

// Create a child class from Human
class Programmer extends Human {

  constructor(name, address, programmingLanguages) {
    super(name, address) // Call the super/parent class constructor, in this case Person.constructor;
    this.programmingLanguages = programmingLanguages;
  }

  introduce() {
    super.introduce(); // Call the super class introduce instance method.
    console.log(`I can write a programming using these languages`, this.programmingLanguages);
  }

  code() {
    console.log(
      "Code some",
      this.programmingLanguages[
        Math.floor(Math.random() * this.programmingLanguages.length)
      ]
    )
  }
}

// Initiate from Human directly
let Obama = new Human("Barrack Obama", "Washington DC");
Obama.introduce() // Hi, my name is Barack Obama

let Fikri = new Programmer("Fikri Rahmat Nurhidayat", "Solo", ["Javascript", "Ruby", "Go", "Kotlin", "Python", "Elixir"]);
Fikri.introduce() // Hi, my name is Fikri; I can write a programming using these languages ["Javascript", "Ruby", "Go", "Kotlin", "Python", "Elixir"]
Fikri.code() // Code some Javascript/Ruby/...
Fikri.work() // Call super class method that isn't overrided or overloaded

try {
  // Obama can't code since Obama is an direct instance of Person, which don't have code method 
  Obama.code() // Error: Undefined method "code"
}

catch(err) {
  console.log(err.message)
}

console.log(Fikri instanceof Human) // true
console.log(Fikri instanceof Programmer) // true

Overriding Method

Overriding a method means that we replace or edit a method from super/parent class without changing the parameters.

class Human {
  
  constructor(name, address) {
    this.name = name;
    this.address = address;
  }

  introduce() {
    console.log(`Hi, my name is ${this.name}`)
  }
}

// Create a child class from Person
class Programmer extends Person {

  constructor(name, address, programmingLanguages) {
    super(name, address) // Call the super/parent class constructor, in this case Person.constructor;
    this.programmingLanguages = programmingLanguages;
  }

  // Override the Introduce Method
  introduce() {
    super.introduce(); // Call the super class introduce instance method.
    console.log(`I can write a programming using these languages`, this.programmingLanguages);
  }

  code() {
    console.log(
      "Code some",
      this.programmingLanguages[
        Math.floor(Math.random * this.programmingLanguages.length)
      ]
    )
  }
}

Overloading Method

Overloading method is where you replace or edit a method and add some new params on it.

class Human {
  
  constructor(name, address) {
    this.name = name;
    this.address = address;
  }

  introduce() {
    console.log(`Hi, my name is ${this.name}`)
  }
}

// Create a child class from Person
class Programmer extends Person {

  constructor(name, address, programmingLanguages) {
    super(name, address) // Call the super/parent class constructor, in this case Person.constructor;
    this.programmingLanguages = programmingLanguages;
  }

  // Overload the Introduce Method
  introduce(withDetail) {
    super.introduce(); // Call the super class introduce instance method.
    if (withDetail) console.log(`I can write a programming using these languages`, this.programmingLanguages);
  }

  code() {
    console.log(
      "Code some",
      this.programmingLanguages[
        Math.floor(Math.random * this.programmingLanguages.length)
      ]
    )
  }
}

Encapsulation

Encapsulation is where you hide some method/property and it won't be accessible outside the class Declaration. It can be some procedure of a process and it can be a secret property. This is handy to prevent bugs, because you explicitly tell people to not use this method outside the class declaration.

class Human {

  static isLivingOnEarth = true;
  static group = "Vertebrate"; 
  
  constructor(name, isAlive) {
    this.name = name; // This will create a property called name
    this.isAlive = isAlive; //
  }

  // Instance method signature
  introduce() {
    console.log(`Hi, my name is ${this.name} and I'm ${this.isAlive ? "alive" : "dead"}`);
  }

  // Check if it can work
  checkWorkCapability() {
    if (!this.isAlive) {
      console.log("I'm dead, I obviously can't work!");
      return false;
    }
  }

  // Prepare for work
  prepareForWork() {
    console.log("Take a bath");
    console.log("Suit up!");
    console.log("On the way to the workplace");
  }

  doWork() {
    console.log("Coding");
    console.log("Get Stuck");
    console.log("Opened up Stack Overflow");
  }

  work() {
    if (!this.checkWorkCapability()) return; // Stop if he/she can't work
    this.prepareForWork();
    this.doWork();
  }

}

let mj = new Human("Michael Jackson", false);
mj.work() // Output: I'm dead, I obviously can't work!

// BUT, mj can access these following methods
mj.doWork();
mj.checkWorkCapability();
mj.prepareForWork();

We don't want that method to be called outside the class declaration. So, can hide them!

Visibility

Before we hide them, I want to talk about the method visibility. There are three method visibility:

  • Public
  • Private
  • Protected

Let's talk about it.

Public

Public means that this method/property is accessible outside the class declaration.

class Human {
  constructor() {
    ...
  }

  // This is public instance method
  introduce() {
    ...
  }

  // This is public static method
  static isEating(...) {
    ...
  }
}

let mj = new Human(...);
mj.introduce() // Running

Human.isEating(...) // Running
Private

Private means that those method/property can't be accessed outside the class declaration.

class Human {
  
  constructor() {
    ...
  }

  #doGossip = () => {
    ...
  }

  talk() {
    ...
    this.#doGossip; // Call the private method
    ...
  }

  static #isHidingInArea51 = true;
}

Human.#isHidingInArea51 // Will return an error!

let mj = new Human(...);
mj.#doGossip // Won't run, will return error!

mj.talk() // Will run, won't return error!

But, it won't run in the child class or family.

class Programmer extends Human {

  constructor() {
    ...
  }

  gossip() {
    this.#doGossip() // Won't run
  }
}
Protected

Protected means that those method/property only accessible in the class declaration and it's family class. By the way, you can't write protected method in Javascript, but you can trick it by using _ prefix to determines other developer that they won't run those method outside class declaration.

class Human {
  
  constructor() {
    ...
  }

  _call() {
    ...
  }
}

class Programmer extends Human {
  
  constructor() {
    ...
  }

  doCall() {
    super._call // Will run
  }
}

let fr = new Programmer();
fr._call // Will also run, but as I said earlier, it is protected by convention. So, it is forbidden to call this method from the outside

Implementation

Now, you've learned about visibility level, let's create a class that has private method.

class User {
  constructor(props) {
    // props is object, because it is better that way
    let { email, password } = props; // Destruct
    this.email = email;
    this.encryptedPassword = this.#encrypt(password); // We won't save the plain password
  }

  // Private method
  #encrypt = (password) => {
    return `pretend-this-is-an-encrypted-version-of-${password}`
  }

  // Getter
  #decrypt = () => {
    return this.encryptedPassword.split(`pretend-this-is-an-encrypted-version-of-`)[1];
  }

  authenticate(password) {
    return this.#decrypt() === password; // Will return true or false
  }
}

let Fikri = new User({
  email: "Fikri@mail.com",
  password: "123456"
})

const isAuthenticated = Fikri.authenticate("123456");
console.log(isAuthenticated) // true

We've successfully hide the #encrypt or #decrypt method, because we don't let anybody use it outside the class, it is dangerous :)

Abstraction

Abstraction is the generalisation of an idea. Let's say you want to create a lot of class from such as Police, Medic, etc. Like, bruh, you can't create a human directly without classify that human, is he/she a medic, police, or even unemployed person. There's nobody who has no classification at all.

To do those things, we need something called abstract class, what is abstract class? It's class where we can't instantiate it, every statement, method signature, properties on that class where means to be implemented by the child class.

class Human {
  
  constructor(props) {
    if (this.constructor === Human) {
      throw new Error("Cannot instantiate from Abstract Class") // Because it's abstract
    }

    let { name, address } = props;
    this.name = name; // Every human has name
    this.address = address; // Every human has address
    this.profession = this.constructor.name; // Every human has profession, and let the child class to define it.
  }

  // Yes, every human can work
  work() {
    console.log("Working...")
  }

  // Every human can introduce
  introduce() {
    console.log(`Hello, my name is ${name}`)
  }

}

class Police extends Human {

  constructor(props) {
    super(props);
    this.rank = props.rank; // Add new property, rank.
  }

  work() {
    console.log("Go to the police station");
    super.work();
  }
}

const Wiranto = new Police({
  name: "Wiranto",
  address: "Unknown",
  rank: "General"
})

console.log(Wiranto.profession); // Police

try {
  let Abstract = new Human({
    name: "Abstract",
    address: "Unknown"
  })
}

catch(err) {
  console.log(err.message) // Cannot instantiate from Abstract Class
}

Polymorphism

Polymorphism means that a class could have a lot of form of its child class, usually act very different from its parent, or even not the same at all. There are tons of implementation of this concept that you can use, but in this case I'll use the mix-ins implementation.

Let's say you have 4 classes, Human (Abstract), Doctor, Police, Writer, and Army. Doctor, Police and Army are the children of Human. Army and Police can shoot, but the Doctor doesn't. But, Army, Police, and Doctor can physically save people. To do this kind of code, we need mix-ins.

class Human {
  constructor(props) {
    // Do something
  }

  introduce() {
    // Do something
  }

  work() {
    console.log(`${this.constructor.name}:`,"Working....")
  }
}

//------------------------------------------------
// Public Server Module/Helper
const PublicServer = Base => class extends Base {
  save() {
    console.log("SFX: Thank You!")
  }
}

// Military Module/Helper
const Military = Base => class extends Base {
  shoot() {
    console.log("DOR!")
  }
}
//------------------------------------------------

class Doctor extends PublicServer(Human) {
  constructor(props) {
    super(props);
  }

  work() {
    super.work(); // From Human Class
    super.save(); // From Public Server Class
  }
}

class Police extends PublicServer(Military(Human)) {
  static workplace = "Police Station";

  constructor(props) {
    super(props);
    this.rank = props.rank;
  }

  work() {
    super.work();
    super.shoot(); // From Military class
    super.save(); // From Public Server Class 
  }
}

class Army extends PublicServer(Military(Human)) {
  static workplace = "Police Station";

  constructor(props) {
    super(props);
    this.rank = props.rank;
  }

  work() {
    super.work();
    super.shoot(); // From Military class
    super.save(); // From Public Server Class 
  }
}

class Writer extends Human {
  
  work() {
    console.log("Write books");
    super.work();
  }
}

/* Instantiate Military Based Class */
const Wiranto = new Police({
  name: "Wiranto",
  address: "Unknown",
  rank: "General"
})

const Prabowo = new Army({
  name: "Prabowo",
  address: "Unknown",
  rank: "General"
})
/* --------------------------------- */

/* -----Instantiate Doctor------ */
const Bambang = new Doctor({
  name: "Andika Bambang",
  address: "Kartopuran"
})
/* ----------------------------- */

/* -----Instantiate Writer------ */
const Noah = new Writer({
  name: "Yuval Noah Harari",
  address: "Sangiran"
})
/* ----------------------------- */

Wiranto.shoot(); // Work
Prabowo.shoot(); // Work

Wiranto.save() // Work
Prabowo.save() // Work
Bambang.save() // Work

Wiranto.work() // Work
Prabowo.work() // Work
Bambang.work() // Work
Noah.work() // Work 

Different Approach on OOP in Javascript

Class declaration is actually new in Javascript, it is implemented on ES6 (2015). So, in the dark age, there is two ways to work with OO in Javascript:

  • Factory Function
  • Constructor Function

In case, you're curious, you can Google it by yourself, or just ask someone about it.

Useful Resource

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment