SOLUTION: Challenge 2 - Access Control & Encapsulation ======================================================= Challenge: Create a BankAccount class with private balance, public deposit/withdraw methods, and a getter for balance. Ensure balance can't go negative. --- SOLUTION: class BankAccount { private _balance: number; readonly accountNumber: string; readonly owner: string; constructor(owner: string, accountNumber: string, initialBalance: number = 0) { this.owner = owner; this.accountNumber = accountNumber; this._balance = Math.max(0, initialBalance); // Can't start negative } // Public getter for balance (read-only) get balance(): number { return this._balance; } // Public deposit method public deposit(amount: number): void { if (amount <= 0) { throw new Error("Deposit amount must be positive"); } this._balance += amount; console.log(`✅ Deposited $${amount}. New balance: $${this._balance.toFixed(2)}`); } // Public withdraw method public withdraw(amount: number): void { if (amount <= 0) { throw new Error("Withdrawal amount must be positive"); } if (amount > this._balance) { throw new Error(`Insufficient funds. Current balance: $${this._balance.toFixed(2)}`); } this._balance -= amount; console.log(`✅ Withdrew $${amount}. New balance: $${this._balance.toFixed(2)}`); } // Transfer between accounts public transferTo(recipient: BankAccount, amount: number): void { if (amount <= 0) { throw new Error("Transfer amount must be positive"); } if (amount > this._balance) { throw new Error(`Insufficient funds to transfer $${amount}`); } this._balance -= amount; recipient._balance += amount; // Private access (same class) console.log( `💸 Transferred $${amount} from ${this.owner} to ${recipient.owner}` ); } // Get account summary public getSummary(): string { return `Account: ${this.accountNumber} | Owner: ${this.owner} | Balance: $${this._balance.toFixed(2)}`; } } // Usage const alice = new BankAccount("Alice", "1001", 1000); const bob = new BankAccount("Bob", "1002", 500); console.log(alice.getSummary()); // Account: 1001 | Owner: Alice | Balance: $1000.00 alice.deposit(500); // ✅ Deposited $500. New balance: $1500.00 console.log(alice.balance); // 1500 alice.withdraw(200); // ✅ Withdrew $200. New balance: $1300.00 // Try to overdraw try { alice.withdraw(2000); } catch (error) { console.error(error.message); // Insufficient funds. Current balance: $1300.00 } // Transfer alice.transferTo(bob, 300); console.log(alice.getSummary()); // Account: 1001 | Owner: Alice | Balance: $1000.00 console.log(bob.getSummary()); // Account: 1002 | Owner: Bob | Balance: $800.00 // Try to access private balance directly // alice._balance = -9999; // ❌ Error: Property '_balance' is private --- EXPLANATION: ENCAPSULATION: Private _balance field is inaccessible from outside. - You can't read it directly (alice._balance — error) - You can't write to it directly (alice._balance = 0 — error) - Only methods inside BankAccount can access it This prevents accidents: - Someone can't accidentally set balance to -999 - You control all state changes through deposit/withdraw - Logic is centralized (all balance changes go through these methods) PUBLIC INTERFACE: deposit(), withdraw(), getSummary() are public - Anyone can call them - They enforce rules (amount must be positive, no overdraft) - They provide a clean, safe API GETTER: get balance(): number { return this._balance; } Lets you read balance like a property: console.log(account.balance) But you can't write: account.balance = 999 // ❌ Error: no setter This is read-only access with validation. READONLY: readonly accountNumber, readonly owner Can only be set in the constructor. Once set, they can't change (like final in Java). PRIVATE TO PRIVATE TRANSFER: In transferTo(), we access recipient._balance directly: recipient._balance += amount This works because both are BankAccount instances. Private means "private to the class", not "private to the instance". --- ACCESS MODIFIERS SUMMARY: public (default): - Accessible from anywhere - Inside class, subclasses, outside private: - Only inside this class - Not in subclasses, not outside protected: - Inside this class + subclasses - Not outside --- BEST PRACTICES: 1. Default to private, open up only what's needed 2. Use getters for read-only access 3. Use methods (not setters) for complex operations 4. Validate in every public method that modifies state 5. Use readonly for immutable properties --- REAL-WORLD EXAMPLE: This pattern is used everywhere: - Banking systems (balance is private) - User accounts (password is private) - Database connections (state is private) - Configuration managers (raw config is private) Encapsulation prevents bugs and makes code maintainable.