Welcome! Today's subject is Encapsulation, a cornerstone of Object-Oriented Programming (OOP). Encapsulation bundles data and the operations that we perform on them into one unit, namely an object. It guards data against unwanted alterations, ensuring the creation of robust and maintainable software.
In TypeScript, encapsulation is enhanced with type annotations, providing additional checks at compile-time to ensure error-free operation. Prepare yourself for an exciting journey as we delve into how encapsulation works in TypeScript and explore the vital role it plays in crafting predictable and type-safe software.
Starting with the basics, encapsulation is the practice of packaging data and the methods that modify this data into a single compartment known as a class
. It safeguards the data in an object from external interference.
To illustrate, consider a TypeScript class
representing a bank account. Without encapsulation, the account balance could be directly altered. With encapsulation, the balance can only change through specified methods, like depositing or withdrawing. TypeScript enhances this with type annotations for class properties and method parameters.
TypeScript1class Account { 2 private balance: number; 3 4 constructor(balance: number) { 5 this.balance = balance; 6 } 7 8 deposit(amount: number): void { 9 this.balance += amount; 10 } 11} 12 13const account = new Account(1000); 14account.deposit(100); 15// The balance can only be updated through the deposit method, protecting it from direct alterations.
Encapsulation restricts direct access to an object's data and prevents unwanted data alteration. This principle is comparable to window blinds, allowing you to look out while preventing others from peeping in.
The private
keyword restricts property access, meaning fields marked as private
cannot be accessed or modified outside the class they belong to. This helps maintain data integrity by preventing unauthorized or accidental changes from other parts of the program. Unlike JavaScript, TypeScript enforces this restriction at compile-time, catching any attempt to access private properties outside their class and preventing code with such errors from running. Constructors
are special methods used for initializing these fields and setting up the initial state of an object. They are automatically called when an object is created from a class.
To illustrate, let's consider a TypeScript class
named Person
, which includes a private field name
.
TypeScript1class Person { 2 private name: string; 3 4 constructor(name: string) { 5 this.name = name; 6 } 7 8 getName(): string { // Accessor method 9 return this.name; 10 } 11} 12 13const person = new Person('Alice'); 14console.log(person.getName()); // Accessing private field via accessor method. Output: Alice 15// Direct access or modification of `name` outside `Person` is prevented.
In this example, name
is private, and getName()
enables us to access name
. We don't allow direct changes to name
externally, promoting data privacy through encapsulation.
Within encapsulation, TypeScript uses getter and setter methods to access or modify private fields. In a class
, the getter method retrieves the field's value, and the setter method alters it. Let's illustrate this.
TypeScript1class Dog { 2 private _name: string; 3 4 constructor(name: string) { 5 this._name = name; 6 } 7 8 set name(newName: string) { // Setter method 9 this._name = newName; 10 } 11 12 get name(): string { // Getter method 13 return this._name; 14 } 15} 16 17const myDog = new Dog('Max'); 18myDog.name = 'Buddy'; 19console.log(myDog.name); // Output: Buddy
Here, set name()
and get name()
serve as the setter and getter methods, respectively, for the private field _name
. The convention of using underscore with private fields is commonly used in TypeScript to differentiate private fields from their public counterparts, especially when using getter and setter methods. While not required, this convention makes code more readable by clearly distinguishing between internal (private) properties and publicly accessible ones.
Getter and setter methods provide a controlled way of accessing and modifying private fields. Getters allow us to retrieve the value of a private property without directly exposing it, while setters allow controlled modification. Using these methods helps us implement validation logic, enforce specific rules when changing values, and ensure that properties are only updated in predictable ways, preserving the integrity of the object's state.
Let's apply the principle of encapsulation to our BankAccount
class, which includes private fields like account number and balance, along with public methods for withdrawals, deposits, and balance checks.
TypeScript1class BankAccount { 2 private accountNo: number; 3 private balance: number; 4 5 constructor(accountNo: number, balance: number) { 6 this.accountNo = accountNo; 7 this.balance = balance; 8 } 9 10 withdraw(amount: number): void { 11 if (amount > this.balance) { 12 throw new Error('Insufficient balance.'); 13 } 14 this.balance -= amount; 15 } 16 17 deposit(amount: number): void { 18 if (amount <= 0) { 19 throw new Error('Deposit amount must be positive.'); 20 } 21 this.balance += amount; 22 } 23 24 checkBalance(): number { 25 return this.balance; 26 } 27} 28 29const account = new BankAccount(1, 500); 30account.withdraw(100); 31account.deposit(50); 32console.log(account.checkBalance()); // Prints: 450
In the above code, the BankAccount
class encapsulates account details, and the public methods manipulate the balance in a controlled, type-safe way.
Admirable! Now it's your turn to apply what you've learned by practicing encapsulation in TypeScript. Utilize type annotations for robust and maintainable code. Remember, practice enhances your comprehension. Enjoy coding!