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.