Object-Oriented Programming (OOP) is a way of organizing code by creating "objects" that represent real-world things. In JavaScript, this approach helps developers write code that is easier to reuse and manage by grouping related data and functions together into these objects.
Key Concepts of OOP
Objects: The fundamental building blocks of OOP. An object is a collection of properties and methods.
Classes: Blueprints for creating objects. Classes define the properties and methods that objects created from the class will have.
Inheritance: Allows one class (child class) to inherit the properties and methods of another class (parent class). This promotes code reusability.
Encapsulation: Bundling the data (properties) and methods (functions) that operate on the data into a single unit or class. It also helps in restricting access to some of the object's components.
Polymorphism: The ability to use a single interface to represent different underlying forms (data types). It allows methods to do different things based on the object it is acting upon.
JavaScript Example: Basic OOP
Let's create a simple example to illustrate these concepts.
Example: Creating a Class for a Person
// Define a class named Person
class Person {
// Constructor method to initialize properties
constructor(name, age) {
this.name = name; // Property
this.age = age; // Property
}
// Method to display a greeting
greet() {
return `Hello, my name is ${this.name} and I am ${this.age} years old.`;
}
// Method to celebrate a birthday
celebrateBirthday() {
this.age++; // Increase age by 1
return `Happy Birthday! I am now ${this.age} years old.`;
}
}
// Create an object from the Person class
const john = new Person('John', 30);
// Use the methods
console.log(john.greet()); // Output: Hello, my name is John and I am 30 years old.
console.log(john.celebrateBirthday()); // Output: Happy Birthday! I am now 31 years old.(code-box)
Explanation of the Code
Class Definition:class Person
creates a new class namedPerson
. This class serves as a blueprint for creating person objects.
constructor(name, age)
is a special method used to initialize new objects. It sets the name
and age
properties when a new Person
object is created.
Properties:this.name
andthis.age
are properties of thePerson
class. They store the name and age of the person.
greet()
is a method that returns a greeting string incorporating the object's properties.celebrateBirthday()
is a method that increments theage
property by 1 and returns a birthday message.
const john = new Person('John', 30)
creates a new Person
object named john
with the name 'John' and age 30.
Using Methods:john.greet()
calls thegreet
method on thejohn
object, which outputs a greeting.john.celebrateBirthday()
calls thecelebrateBirthday
method, which increments the age and outputs a birthday message.
Inheritance Example
Let’s extend this example to include inheritance, where a Student
class inherits from the Person
class.
// Define a class named Student that extends Person
class Student extends Person {
constructor(name, age, studentId) {
super(name, age); // Call the parent class's constructor
this.studentId = studentId; // Additional property for Student
}
// Method to display student ID
displayId() {
return `My student ID is ${this.studentId}.`;
}
}
// Create an object from the Student class
const jane = new Student('Jane', 22, 'S12345');
// Use the methods from both Person and Student classes
console.log(jane.greet()); // Output: Hello, my name is Jane and I am 22 years old.
console.log(jane.displayId()); // Output: My student ID is S12345.(code-box)
Explanation of Inheritance Example
Subclass Definition:class Student extends Person
creates a new classStudent
that inherits from thePerson
class.
super(name, age)
calls the Person
class's constructor to initialize the inherited properties.
Additional Property:this.studentId
adds a new property specific to the Student
class.
New Method:displayId()
is a method specific to the Student
class that displays the student ID.
Creating and Using Objects:const jane = new Student('Jane', 22, 'S12345')
creates a newStudent
object.jane.greet()
uses thegreet
method from thePerson
class, whilejane.displayId()
uses the method from theStudent
class.
This example demonstrates how inheritance allows extending existing classes with additional functionality while reusing the code from parent classes.
Encapsulation
Encapsulation is the concept of bundling data (properties) and methods (functions) that operate on the data into a single unit or class. It also involves restricting access to some of the object’s components to protect the integrity of the data.
Example of Encapsulation
class BankAccount {
#balance; // Private field
constructor(accountNumber, initialBalance) {
this.accountNumber = accountNumber;
this.#balance = initialBalance;
}
// Method to deposit money
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
return `Deposited ${amount}. New balance is ${this.#balance}.`;
} else {
return 'Deposit amount must be positive.';
}
}
// Method to withdraw money
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
return `Withdrew ${amount}. New balance is ${this.#balance}.`;
} else {
return 'Invalid withdrawal amount or insufficient funds.';
}
}
// Method to check balance
getBalance() {
return `Current balance is ${this.#balance}.`;
}
}
// Create a BankAccount object
const myAccount = new BankAccount('12345678', 1000);
console.log(myAccount.deposit(500)); // Output: Deposited 500. New balance is 1500.
console.log(myAccount.withdraw(200)); // Output: Withdrew 200. New balance is 1300.
console.log(myAccount.getBalance()); // Output: Current balance is 1300.
console.log(myAccount.#balance); // SyntaxError: Private field '#balance' must be declared in an enclosing class(code-box)
Explanation
Private Field:#balance
is a private field. It can only be accessed within the BankAccount
class. This prevents external code from modifying the balance directly, ensuring the integrity of the balance.
Methods:deposit(amount)
andwithdraw(amount)
methods manipulate the private#balance
field. They include checks to ensure valid operations.getBalance()
provides controlled access to the balance without allowing direct modifications.
Polymorphism
Polymorphism allows methods to have the same name but behave differently depending on the object or context. It enables objects to be treated as instances of their parent class rather than their actual class, allowing for a flexible and interchangeable code structure.
Example of Polymorphism
Let’s extend our previous example with polymorphism. We’ll define a parent class and a couple of subclasses that override a method.
class Animal {
speak() {
return 'Animal makes a sound';
}
}
class Dog extends Animal {
speak() {
return 'Woof! Woof!';
}
}
class Cat extends Animal {
speak() {
return 'Meow! Meow!';
}
}
// Function to make animals speak
function makeAnimalSpeak(animal) {
console.log(animal.speak());
}
// Create instances of Dog and Cat
const myDog = new Dog();
const myCat = new Cat();
// Use polymorphism
makeAnimalSpeak(myDog); // Output: Woof! Woof!
makeAnimalSpeak(myCat); // Output: Meow! Meow!(code-box)
Explanation
Parent Class:Animal
is the parent class with a speak()
method that provides a default implementation.
Child Classes:Dog
and Cat
extend Animal
and override the speak()
method to provide specific implementations for dogs and cats, respectively.
Polymorphism in Action:- The
makeAnimalSpeak()
function accepts anAnimal
object. Because of polymorphism, it can accept any object that inherits fromAnimal
and call thespeak()
method on it. - Depending on whether the object is a
Dog
or aCat
, thespeak()
method behaves differently.
By using encapsulation, we protect data within objects and ensure that it is accessed in controlled ways. Polymorphism allows for more flexible and reusable code by letting methods operate on objects of different classes in a consistent manner.