Hello! In this lesson, we're revisiting Encapsulation, Private Attributes, and Private Methods in Object-Oriented Programming (OOP). Imagine encapsulation as an invisible fence safeguarding a garden from outside interference, keeping data and methods safe within. Within this garden, certain plants (Private Attributes and Methods) are only for the gardener's eyes. These are crucial for making your classes more robust and secure!
Encapsulation in OOP wraps up data and methods into a class
. This organizational approach tidies the code and reinforces security. If you were to code a multiplayer game, for example, you could create a Player
class, encapsulating data (health
, armor
, stamina
) and methods (receiveDamage
, shieldHit
, restoreHealth
).
TypeScript1class Player { 2 private health: number; 3 private armor: number; 4 private stamina: number; 5 6 constructor(health: number, armor: number, stamina: number) { 7 this.health = health; 8 this.armor = armor; 9 this.stamina = stamina; 10 } 11 12 receiveDamage(damage: number): void { 13 this.health -= damage; 14 } 15 16 shieldHit(armor: number): void { 17 this.armor -= armor; 18 } 19 20 restoreHealth(healthIncrease: number): void { 21 this.health += healthIncrease; 22 } 23} 24 25const player = new Player(100, 50, 77);
Now, player
is an instance of the Player
class on which you can call methods. You may notice the private
keyword in the Player
class definition; we will discuss the private
keyword in the next section.
In TypeScript, private attributes and methods are designated using the private
keyword. Note that the constructor itself cannot be private.
TypeScript1class PrivateExample { 2 private privateAttribute: string; 3 4 constructor() { 5 this.publicAttribute = "Public"; 6 this.privateAttribute = "Private"; 7 } 8 9 publicAttribute: string; 10 11 getPrivateAttribute(): string { 12 return this.privateAttribute; 13 } 14} 15 16const example = new PrivateExample(); 17console.log(example.publicAttribute); // Works: logs "Public" 18console.log(example.getPrivateAttribute()); // Works: logs "Private" 19// console.log(example.privateAttribute); // Error: can't access private attribute from outside
Private attributes and methods are inaccessible directly from an instance. This arrangement helps maintain integrity.
Private Attributes, which can only be altered via class methods, limit outside interference. For instance, a BankAccount
class might feature a balance
private attribute that one could change only through deposits or withdrawals.
TypeScript1class BankAccount { 2 private balance: number; 3 accountNumber: number; 4 5 constructor(accountNumber: number, balance: number) { 6 this.accountNumber = accountNumber; 7 this.balance = balance; 8 } 9 10 deposit(amount: number): void { 11 this.balance += amount; 12 } 13 14 getBalance(): number { 15 return this.balance; 16 } 17} 18 19const bankAccount = new BankAccount(1234, 100); 20console.log(bankAccount.getBalance()); // Works: logs 100 21// console.log(bankAccount.balance); // Error: can't access private attribute from outside
Here, balance
is private, thus ensuring the integrity of the account balance. It can't be accessed directly from outside the class.
Like private attributes, private methods are accessible only within their class. Here's an example:
TypeScript1class BankAccount { 2 private balance: number; 3 accountNumber: number; 4 5 constructor(accountNumber: number, balance: number) { 6 this.accountNumber = accountNumber; 7 this.balance = balance; 8 } 9 10 private addInterest(interestRate: number): void { 11 this.balance += this.balance * interestRate; 12 } 13 14 addYearlyInterest(): void { 15 this.addInterest(0.02); // Adds 2% interest 16 } 17 18 getBalance(): number { 19 return this.balance; 20 } 21} 22 23const bankAccount = new BankAccount(1234, 100); 24bankAccount.addYearlyInterest(); // Works, calling a public method 25console.log(bankAccount.getBalance()); // Logs the updated balance 26// bankAccount.addInterest(0.1); // Error: can't call a private method from outside
Within the BankAccount class, the public method addYearlyInterest
calls the private method addInterest
. Although private methods like addInterest
can't be called from outside the class, they can still be used internally by class methods. Such private methods are useful when you want to encapsulate functionalities that are not meant to be exposed externally but are utilized to support the class operations internally.
In TypeScript, getters and setters provide a way to access and mutate private attributes indirectly while maintaining control over how values are retrieved or changed. This remains in line with the encapsulation principle by providing a controlled interface to interact with private data.
Getters allow access to the value of a private attribute in a safe, controlled manner. You can define a getter for a private attribute by using the get
keyword followed by the method name. Here's an example demonstrating the use of a getter method:
TypeScript1class BankAccount { 2 private balance: number; 3 accountNumber: number; 4 5 constructor(accountNumber: number, balance: number) { 6 this.accountNumber = accountNumber; 7 this.balance = balance; 8 } 9 10 get balanceValue(): number { 11 return this.balance; 12 } 13 14 deposit(amount: number): void { 15 this.balance += amount; 16 } 17} 18 19const bankAccount = new BankAccount(1234, 100); 20console.log(bankAccount.balanceValue); // Works: logs 100
In this example, the balanceValue
getter method provides a safe way to access the private balance
attribute.
Setters allow modification of the value of a private attribute while providing a controlled interface. To create a setter method, you should use the set
keyword followed by the method name. Here's an example demonstrating the use of a setter method:
TypeScript1class BankAccount { 2 private balance: number; 3 accountNumber: number; 4 5 constructor(accountNumber: number, balance: number) { 6 this.accountNumber = accountNumber; 7 this.balance = balance; 8 } 9 10 get balanceValue(): number { 11 return this.balance; 12 } 13 14 set balanceValue(amount: number) { 15 if (amount >= 0) { 16 this.balance = amount; 17 } else { 18 console.log("Balance cannot be negative."); 19 } 20 } 21 22 deposit(amount: number): void { 23 this.balance += amount; 24 } 25} 26 27const bankAccount = new BankAccount(1234, 100); 28bankAccount.balanceValue = 200; // Works: sets balance to 200 29console.log(bankAccount.balanceValue); // Works: logs 200 30bankAccount.balanceValue = -50; // Logs: "Balance cannot be negative."
In this example, the balanceValue
setter method ensures that the balance is only set to non-negative values, adding a layer of validation.
Getters and setters provide a controlled way to access and modify private attributes. By using a getter, we offer read-only access to private attributes without exposing the attribute itself. A setter can enforce rules, like ensuring a balance attribute remains non-negative. This maintains encapsulation by letting external code interact with private data safely and predictably.
Great job refreshing your understanding of encapsulation, private attributes, and private methods concepts in TypeScript! Correctly understanding and applying these foundational principles of OOP makes your code concise, robust, and secure. With TypeScript, the advantages of static type checking and type annotations enhance clarity and reliability, ensuring that your code adheres to strong typing practices for improved maintainability.
Coming up next is hands-on practice. Keep up the good work — exciting exercises are just around the corner!