In this chapter, we will learn about JavaScript encapsulation. Encapsulation is one of the fundamental principles of Object-Oriented Programming (OOP). It refers to the practice of bundling data and methods that operate on that data within a single unit or class and restricting access to some of the object’s components. We will cover:
- What is Encapsulation?
- Why Use Encapsulation?
- Implementing Encapsulation
- Using Private Fields
- Using Getters and Setters
- Simple Programs using Encapsulation
What is Encapsulation?
Encapsulation is the practice of keeping the internal state of an object hidden from the outside world. This is done by restricting direct access to some of an object’s components and only exposing a controlled interface for interacting with the object.
Why Use Encapsulation?
Encapsulation provides several benefits:
- Data Protection: Prevents external code from directly modifying the internal state of an object, reducing the risk of unintended side effects and bugs.
- Modularity: Encourages a modular design by bundling related data and methods together.
- Maintainability: Makes code easier to maintain and understand by providing a clear interface for interacting with objects.
- Abstraction: Hides the internal implementation details and exposes only the necessary functionality.
Implementing Encapsulation
Encapsulation can be implemented using private fields, getters, and setters.
Using Private Fields
In JavaScript, you can use the # symbol to create private fields within a class. Private fields are only accessible within the class and are not exposed to the outside world.
Example
class Person {
// Private fields
#firstName;
#lastName;
constructor(firstName, lastName) {
this.#firstName = firstName;
this.#lastName = lastName;
}
// Public method to get full name
getFullName() {
return `${this.#firstName} ${this.#lastName}`;
}
}
let person = new Person("Ramesh", "Fadatare");
console.log(person.getFullName());
console.log(person.#firstName); // This will cause an error
Output:
Ramesh Fadatare
SyntaxError: Private field '#firstName' must be declared in an enclosing class
Using Getters and Setters
Getters and setters provide a controlled way to access and modify the properties of an object. They allow you to add custom logic and validation.
Example
class Person {
#firstName;
#lastName;
constructor(firstName, lastName) {
this.#firstName = firstName;
this.#lastName = lastName;
}
// Getter for full name
get fullName() {
return `${this.#firstName} ${this.#lastName}`;
}
// Setter for full name
set fullName(name) {
let parts = name.split(' ');
this.#firstName = parts[0];
this.#lastName = parts[1];
}
}
let person = new Person("Ramesh", "Fadatare");
console.log(person.fullName);
person.fullName = "Neha Sharma";
console.log(person.fullName);
Output:
Ramesh Fadatare
Neha Sharma
Simple Programs using Encapsulation
Program 1: Bank Account with Encapsulation
class BankAccount {
#balance;
constructor(accountNumber, accountHolder, balance) {
this.accountNumber = accountNumber;
this.accountHolder = accountHolder;
this.#balance = balance;
}
// Getter for balance
get balance() {
return this.#balance;
}
// Method to deposit money
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
}
}
// Method to withdraw money
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
} else {
console.log("Insufficient funds");
}
}
}
let account = new BankAccount("1234567890", "Rahul Sharma", 5000);
console.log("Initial Balance:", account.balance);
account.deposit(2000);
console.log("Balance after deposit:", account.balance);
account.withdraw(1000);
console.log("Balance after withdrawal:", account.balance);
account.withdraw(7000); // This should trigger insufficient funds message
Output:
Initial Balance: 5000
Balance after deposit: 7000
Balance after withdrawal: 6000
Insufficient funds
Program 2: Employee Management with Encapsulation
class Employee {
#salary;
constructor(name, position, salary) {
this.name = name;
this.position = position;
this.#salary = salary;
}
// Getter for salary
get salary() {
return this.#salary;
}
// Setter for salary with validation
set salary(amount) {
if (amount > 0) {
this.#salary = amount;
} else {
console.log("Salary must be a positive number");
}
}
// Method to get employee details
getDetails() {
return `${this.name}, ${this.position}, Salary: ${this.#salary}`;
}
}
let emp = new Employee("Neha Sharma", "Manager", 80000);
console.log(emp.getDetails());
emp.salary = 90000; // Valid update
console.log(emp.getDetails());
emp.salary = -5000; // Invalid update
Output:
Neha Sharma, Manager, Salary: 80000
Neha Sharma, Manager, Salary: 90000
Salary must be a positive number
Program 3: Student Grades with Encapsulation
class Student {
#grades;
constructor(name, grades) {
this.name = name;
this.#grades = grades;
}
// Getter for grades
get grades() {
return this.#grades;
}
// Setter for grades with validation
set grades(newGrades) {
if (Array.isArray(newGrades) && newGrades.every(grade => grade >= 0 && grade <= 100)) {
this.#grades = newGrades;
} else {
console.log("All grades should be between 0 and 100");
}
}
// Getter for average grade
get averageGrade() {
let sum = this.#grades.reduce((acc, grade) => acc + grade, 0);
return sum / this.#grades.length;
}
}
let student = new Student("Priya", [85, 90, 78]);
console.log("Initial Grades:", student.grades);
console.log("Average Grade:", student.averageGrade);
student.grades = [88, 92, 80]; // Valid update
console.log("Updated Grades:", student.grades);
console.log("New Average Grade:", student.averageGrade);
student.grades = [105, 90, 78]; // Invalid update
Output:
Initial Grades: [ 85, 90, 78 ]
Average Grade: 84.33333333333333
Updated Grades: [ 88, 92, 80 ]
New Average Grade: 86.66666666666667
All grades should be between 0 and 100
Conclusion
In this chapter, you learned about JavaScript encapsulation, including its benefits and how to implement it using private fields, getters, and setters. We also provided various use cases with simple programs to demonstrate the usage of encapsulation. Encapsulation is a fundamental concept in OOP that helps protect the internal state of objects and promotes better code organization.