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.
- 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.
- Overview
- Goals
- Table of Content
- Preliminary
- Four Pillars of OOP
- Different Approach on OOP in Javascript
- Useful Resource
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);
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 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 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 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 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
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
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 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
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
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.
There are 4 pillars in OOP, pillar means concept.
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 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 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 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!
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 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 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 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
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 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 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
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.
- Videos
- Articles
- Mozilla Developer Network [Recommended]
- Medium