Encapsulation is a concept derived from object-oriented programming (OOP) that consists of hiding and protecting the internal details of a class
Encapsulation has two main aspects:
- Hiding the internal state of objects: Objects can have private properties that are not directly accessible from outside the class.
- Providing a public interface: The class can expose public methods that allow controlled interaction with its properties.
In other words, it is about protecting direct access to data and exposing only what is necessary for other parts of the program to interact with that class.
By hiding the internal state of objects and providing a controlled public interface, we ensure that our objects are more robust, easier to maintain, and less prone to errors.
If you want to learn more, you can check
Private Properties
In JavaScript, ES2022 introduced the private fields syntax. To do this, the property name is prefixed with #
.
Properties that use this prefix are only accessible within the class in which they are defined (they cannot be accessed or modified from outside the class).
class Person {
#name;
constructor(name) {
this.#name = name;
}
getName() {
return this.#name;
}
}
const person1 = new Person("Juan");
// Correct access through a public method
console.log(person1.getName()); // "Juan"
// This will cause an error: "Cannot read private member"
console.log(person1.#name); // Error
In this example:
#name
is a private property. It can only be accessed within thePerson
class.getName()
is a public method that allows access to the value of#name
.
Private Methods
Methods can also be private. Like properties, they are defined with the #
prefix:
class User {
#secretKey;
constructor(key) {
this.#secretKey = key;
}
#showKey() {
console.log(`The secret key is: ${this.#secretKey}`);
}
authenticate() {
this.#showKey(); // Valid call from within the class
}
}
const user1 = new User("12345");
user1.authenticate(); // The secret key is: 12345
// This will cause an error: "Cannot read private member"
// user1.#showKey(); // Error
In this example, #showKey()
is a private method. It can only be called from within the class, not from outside.
Getters and Setters
Encapsulation fits very well with the use of getter and setter methods.
class Person {
#age;
constructor(age) {
this.#age = age;
}
get age() {
return this.#age;
}
set age(newAge) {
if (newAge > 0) {
this.#age = newAge;
} else {
console.log("Age must be positive.");
}
}
}
const person2 = new Person(25);
console.log(person2.age); // 25
person2.age = 30; // Modifies age
console.log(person2.age); // 30
person2.age = -5; // Age must be positive.
In this example:
- The getter
age
allows access to the value of the private property#age
. - The setter
age
allows modifying the value of#age
only if it is a positive number.
Simple Example of Encapsulation
Let’s look at a basic example of how data can be encapsulated within a class:
class BankAccount {
// Private property (accessed only from the class)
#balance;
constructor(initial) {
this.#balance = initial;
}
// Public method to get the balance
getBalance() {
return this.#balance;
}
// Public method to deposit money
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
} else {
console.log("The amount must be positive.");
}
}
// Public method to withdraw money
withdraw(amount) {
if (amount <= this.#balance) {
this.#balance -= amount;
} else {
console.log("Insufficient funds.");
}
}
}
const account1 = new BankAccount(1000);
console.log(account1.getBalance()); // 1000
account1.deposit(500);
console.log(account1.getBalance()); // 1500
account1.withdraw(200);
console.log(account1.getBalance()); // 1300
In this example:
- The property
#balance
is private and cannot be accessed directly from outside the class. - The methods
deposit()
,withdraw()
, andgetBalance()
allow controlled interaction with the account balance.
We also could have used setter and getter methods, but this way we see another form with normal methods.