Advanced OOP
ποΈ Advanced OOP
π· Abstract Classes: Enforce Contracts
An abstract class can't be instantiated. It defines a contract that subclasses must implement:
abstract class Animal { abstract makeSound(): void; // Subclasses MUST implement sleep() { console.log("Zzz..."); // Concrete method (optional to override) } } class Dog extends Animal { makeSound() { console.log("Woof!"); // β Implements abstract method } } const animal = new Animal(); // β Error: can't instantiate abstract class const dog = new Dog(); // β OK dog.makeSound(); // "Woof!"
Abstract Methods
abstract methodName(); β no body. Subclasses must implement.
Abstract Properties
abstract prop: Type; β subclasses must define.
Concrete Methods
Regular methods with bodies. Subclasses can override or use as-is.
Use Case
Define a template. Subclasses fill in the details.
π Access Modifiers: Control Visibility
TypeScript provides public, private, and protected to control who can access members:
class BankAccount { public accountHolder: string; // Anyone can read/write private balance: number = 0; // Only this class can access protected log: string[] = []; // This class and subclasses constructor(holder: string) { this.accountHolder = holder; } public deposit(amount: number) { this.balance += amount; this.recordLog(`Deposited ${amount}`); } private recordLog(message: string) { this.log.push(message); } protected getBalance() { return this.balance; // Only subclasses can call } } const account = new BankAccount("Alice"); account.deposit(100); // β public console.log(account.accountHolder); // β public // account.balance; // β private β error // account.recordLog("..."); // β private β error
Access Levels
public (default)
public prop: string; // Accessible everywhere
private
private prop: string; // Only inside this class
protected
protected prop: string; // This class + subclasses
readonly
readonly prop: string; // Can't reassign after init
π Static Members: Class-Level Data
Static members belong to the class itself, not instances:
class Counter { static count: number = 0; static reset() { Counter.count = 0; } constructor() { Counter.count++; // Increment class-level counter } } const c1 = new Counter(); const c2 = new Counter(); console.log(Counter.count); // 2 β shared across all instances Counter.reset(); console.log(Counter.count); // 0
Static Properties
static prop: Type; β shared by all instances.
Static Methods
static methodName() { } β called on the class, not instances.
Access
Use ClassName.property, not instance.property.
Use Cases
Configuration, factories, utility functions, singletons.
π― Getters & Setters: Computed Properties
Getters and setters let you use properties while running custom logic:
class User { private _age: number = 0; get age(): number { return this._age; } set age(value: number) { if (value < 0) { throw new Error("Age can't be negative"); } this._age = value; } } const user = new User(); user.age = 25; // β Calls setter with validation console.log(user.age); // β Calls getter user.age = -5; // β Error: Age can't be negative
Why use getters/setters?
- Validation: Check constraints before setting
- Computed values: Calculate on-the-fly
- Side effects: Log, notify, update cache
- Clean API: Property syntax instead of methods
ποΈ Real-World Pattern: Sealed Class with Private State
Robust Configuration Class
abstract class BaseConfig { abstract validate(): boolean; } class AppConfig extends BaseConfig { private _port: number; private _host: string; readonly version: string; static instance: AppConfig; private constructor(port: number, host: string) { super(); this._port = port; this._host = host; this.version = "1.0.0"; } static create(port: number, host: string): AppConfig { if (!AppConfig.instance) { AppConfig.instance = new AppConfig(port, host); } return AppConfig.instance; } get port(): number { return this._port; } set port(value: number) { if (value < 1 || value > 65535) { throw new Error("Invalid port"); } this._port = value; } validate(): boolean { return this._port > 0 && this._host.length > 0; } } // Usage: factory pattern with singleton const config = AppConfig.create(3000, "localhost"); console.log(config.port); // 3000
π» Coding Challenges
Challenge 1: Abstract Class with Multiple Subclasses
Create an abstract Vehicle class with abstract methods. Implement two subclasses (Car, Bike) that satisfy the contract.
Goal: Practice abstract classes and polymorphism.
Challenge 2: Access Control & Encapsulation
Create a BankAccount class with private balance, public deposit/withdraw methods, and a getter for balance. Ensure balance can't go negative.
Goal: Understand private/public boundaries and encapsulation.
Challenge 3: Static Singleton Pattern
Create a Database class with a static getInstance() method that returns a single instance (singleton). Add static initialization logic.
Goal: Implement the singleton pattern with static members.
TypeScript's private is compile-time only. In the compiled JavaScript, private fields are just regular propertiesβthey're not truly inaccessible at runtime. For true privacy, use JavaScript's # private fields. But for most purposes, TypeScript's private is sufficient discipline.
π― What's Next
With advanced OOP patterns mastered, we'll explore Type Utilities & Inference β leveraging TypeScript's powerful type system to build reusable, composable type utilities.