JavaScript since ES2015 is quite powerful Object-Oriented Language.
By using classes we can make our code cleaner, more readable and reusable.
There are 4 code principles in object-oriended programming and without them programming language can’t be named object-oriented one:
- Encapsulation
- Abstraction
- Inheritance
- Polymorphism
Encapsulation
The idea of encapsulation is that code details should be not visible to “the outside”. For example, let’s define a User class.
class User {
}
When the principle of encapsulation is implemented all properties of this class are hidden from other classes and not visible for the users. These properties of course are accessible but only by using public access methods.
To implement encapsulation in JS, let’s declare new property in the User class and make it private. This will make the property hidden (private fields were added in ES2015). Right now the property will be only accessible by using the methods declared inside the class.
class User {
// Create private class property (they are available since ECMAScript® 2015)
#_username;
}
The next step is to declare this public setter and getter methods for the private property.
In the example below you can see a hidden #_username property, the getter method and the setter method.
class User {
// Create private class property (they are available since ECMAScript® 2015)
#_username;
// Create GETTER method for getting username property
get username() {
return this.#_username;
}
// Create SETTER method for setting or changing username
set username(newUsername) {
if (newUsername && newUsername.length < 3) {
throw new Error("Username must contain at least 3 characters.");
}
this.#_username = newUsername;
}
}
When you want to set the #_username property, you must call the setter method and when you try to access the #_username property, you have to invoke the getter method.
class User {
// Create private class property (they are available since ECMAScript® 2015)
#_username;
}
class User {
// Create private class property (they are available since ECMAScript® 2015)
#_username;
// Create GETTER method for getting username property
get username() {
return this.#_username;
}
// Create SETTER method for setting or changing username
set username(newUsername) {
if (newUsername && newUsername.length < 3) {
throw new Error("Username must contain at least 3 characters.");
}
this.#_username = newUsername;
}
}
// Create new instance of User class
let anne = new User();
// Set username - this calls username SETTER method
anne.username = "anne";
// Access username - this calls username GETTER method
console.log(anne.username);
With this implementation, the username field is private and can be only accessible or changeable through the setter and getter methods.
Inheritance
Inheritance allows the parent class to pass functionality to a child class, creating a clean and reusable code without unnecessary repeats. This actually makes sense because in the real world objects are often quite similar. For example, car and motorcycle are both vehicles and they both can be driven.
Let’s create a Car class with constructor and drive method:
class Car {
constructor(speed) {
this.speed = speed;
}
drive() {
console.log(`The vehicle is going at ${this.speed}`);
}
}
The next step is to create a MotorCycle class that extends the Car class. For this purpose we should use the ‘extends’ keyword.
class Car {
constructor(speed) {
this.speed = speed;
}
drive() {
console.log(`The vehicle is going at ${this.speed}`);
}
}
class MotorCycle extends Car {}
Right now the MotorCycle is a subclass of the Car class and we can see that the Motorcycle class inherits the constructor and the drive function from the Car class.
class Car {
constructor(speed) {
this.speed = speed;
}
drive() {
console.log(`The vehicle is going at ${this.speed}`);
}
}
class MotorCycle extends Car {}
const moto = new MotorCycle("140km/h");
console.log(moto); // MotorCycle {speed: '140km/h'}
Polymorphism
Polymorphism means ‘many forms’ nad is actually a quite simple concept. It’s the ability of using the same method on a different objects.
For example the MotorCycle and the Bike classes have the same function - drive.
class MotorCycle {
drive() {
console.log("Really fast");
}
}
class Bike extends MotorCycle {
drive() {
console.log("Quite slow");
}
}
const moto = new MotorCycle();
const bike = new Bike();
moto.drive(); // Really fast
bike.drive(); // Quite fast
The MotorCycle class has drive() method and the Bike class also has it’s own implementation of drive() method. In both classes, the drive() method return a different result.
Abstraction
Abstraction is a principle that says that only information that is relevant to the problem’s context should be represented by the class. For example let’s look at the car. We can see external parts such as windows, tyres, doors, car mirrors, rims etc. What we cannot see is for example engine parts.
Hiding details and showing only simple, relevant informations, that’s what abstraction is.