Learn how to control access to class members in TypeScript using public, private, and protected modifiers for better encapsulation and data protection.
In the world of programming, especially in object-oriented programming (OOP), controlling access to the internal state of objects is crucial. This is where access modifiers come into play. In TypeScript, we have three primary access modifiers: public, private, and protected. These modifiers help us define the accessibility of class members (properties and methods) and enforce encapsulation—a core principle of OOP.
The public modifier is the default access level for class members in TypeScript. When a member is marked as public, it can be accessed from anywhere: inside the class, outside the class, and even from subclasses.
class Car {
public brand: string;
constructor(brand: string) {
this.brand = brand;
}
public displayBrand(): void {
console.log(`The car brand is ${this.brand}.`);
}
}
const myCar = new Car("Toyota");
myCar.displayBrand(); // Output: The car brand is Toyota.
console.log(myCar.brand); // Output: Toyota
In this example, both the brand property and the displayBrand method are accessible from outside the Car class.
The private modifier restricts access to the class member such that it can only be accessed within the class itself. This is useful for hiding the internal implementation details and protecting the integrity of the data.
class BankAccount {
private balance: number;
constructor(initialBalance: number) {
this.balance = initialBalance;
}
public deposit(amount: number): void {
this.balance += amount;
}
public getBalance(): number {
return this.balance;
}
}
const myAccount = new BankAccount(1000);
myAccount.deposit(500);
console.log(myAccount.getBalance()); // Output: 1500
// console.log(myAccount.balance); // Error: Property 'balance' is private and only accessible within class 'BankAccount'.
Here, the balance property is private, ensuring that it cannot be accessed directly from outside the BankAccount class. Instead, we use the getBalance method to retrieve its value.
The protected modifier is similar to private, but it allows access to the member within the class and its subclasses. This is particularly useful when you want to allow subclasses to access certain properties or methods while keeping them hidden from the outside world.
class Animal {
protected name: string;
constructor(name: string) {
this.name = name;
}
protected makeSound(): void {
console.log(`${this.name} makes a sound.`);
}
}
class Dog extends Animal {
public bark(): void {
console.log(`${this.name} barks.`);
this.makeSound();
}
}
const myDog = new Dog("Buddy");
myDog.bark(); // Output: Buddy barks. Buddy makes a sound.
// console.log(myDog.name); // Error: Property 'name' is protected and only accessible within class 'Animal' and its subclasses.
In this example, the name property and makeSound method are protected, allowing the Dog class to access them, but they remain inaccessible from outside the class hierarchy.
Encapsulation is a fundamental concept in OOP that involves bundling the data (properties) and the methods that operate on the data into a single unit, or class. By using access modifiers, we can control how the data is accessed and modified, which leads to several benefits:
One of the advantages of using TypeScript is its ability to enforce access levels at compile time. This means that if you try to access a private or protected member from outside its allowed scope, TypeScript will throw a compile-time error, preventing potential runtime errors and bugs.
class Example {
private secret: string = "hidden";
public revealSecret(): string {
return this.secret;
}
}
const example = new Example();
// console.log(example.secret); // Error: Property 'secret' is private and only accessible within class 'Example'.
console.log(example.revealSecret()); // Output: hidden
In this code, attempting to access the secret property directly results in a compile-time error, ensuring that the encapsulation is respected.
Let’s put our understanding of access modifiers into practice. Try modifying the access levels of class members in the following exercises and observe the outcomes.
Person with a private property age and a public method getAge.age property directly from an instance of Person.age property to protected and create a subclass Employee that accesses age.age public and observe the changes.class Person {
private age: number;
constructor(age: number) {
this.age = age;
}
public getAge(): number {
return this.age;
}
}
// Step 2
const person = new Person(30);
// console.log(person.age); // Error: Property 'age' is private and only accessible within class 'Person'.
// Step 3
class Employee extends Person {
public displayAge(): void {
console.log(`Employee age is ${this.getAge()}.`);
}
}
const employee = new Employee(25);
employee.displayAge(); // Output: Employee age is 25.
// Step 4
class PublicPerson {
public age: number;
constructor(age: number) {
this.age = age;
}
}
const publicPerson = new PublicPerson(40);
console.log(publicPerson.age); // Output: 40
Library with a private array books and methods to addBook and listBooks.addBook method.books array directly.class Library {
private books: string[] = [];
public addBook(book: string): void {
this.books.push(book);
}
public listBooks(): void {
console.log("Books in the library:", this.books.join(", "));
}
}
const myLibrary = new Library();
myLibrary.addBook("The Great Gatsby");
myLibrary.addBook("1984");
myLibrary.listBooks(); // Output: Books in the library: The Great Gatsby, 1984
// Attempt to modify books directly
// myLibrary.books.push("Moby Dick"); // Error: Property 'books' is private and only accessible within class 'Library'.
Now it’s your turn! Modify the code examples above by changing the access modifiers and observe how the accessibility of class members changes. Experiment with creating subclasses and see how protected members behave differently from private ones.
To better understand how access modifiers work, let’s visualize the accessibility of class members using a simple diagram.
classDiagram
class Car {
+brand: string
+displayBrand(): void
}
class BankAccount {
-balance: number
+deposit(amount: number): void
+getBalance(): number
}
class Animal {
#name: string
#makeSound(): void
}
class Dog {
+bark(): void
}
Animal <|-- Dog
In this diagram:
Car has a public property and method, accessible from anywhere.BankAccount has a private property, accessible only within the class.Animal has a protected property and method, accessible within the class and its subclass Dog.By understanding and utilizing access modifiers, we can write more robust, maintainable, and secure TypeScript code.