Introduction and Lesson Goal

Today's mission involves using multiple Object-Oriented Programming (OOP) principles to tackle complex tasks in TypeScript. When principles like Encapsulation, Abstraction, Polymorphism, and Composition are integrated, the resulting code becomes streamlined and easier to manage.

Our goal is to dissect two real-world examples, gaining insights into how these principles can seamlessly orchestrate solutions.

Real-life Example 1: Building an Online Library System

Let's design an online library system to reinforce our understanding of Encapsulation and Polymorphism. Encapsulation will help us protect the attributes of books, members, and transactions, ensuring they are accessible in a controlled manner using TypeScript's features. Polymorphism will demonstrate its power by enabling a single interface to represent different underlying forms, such as digital and print versions of books.

// Base class for different types of library users
class Member {
    private name: string;

    constructor(name: string) {
        this.name = name;
    }

    checkOutBook(book: Book): void {
        console.log(`${this.name} checked out ${book.getBookType()} book ${book.getTitle()}.`);
    }
}

// Base class for different types of books
abstract class Book {
    protected title: string;

    constructor(title: string) {
        this.title = title;
    }
    
    public getTitle(): string {
        return this.title
    };

    abstract getBookType(): string;
    
}

// Inherits from Book, represents a digital book
class DigitalBook extends Book {
    getBookType(): string {
        return "Digital";
    }
}

// Inherits from Book, represents a physical book
class PhysicalBook extends Book {
    getBookType(): string {
        return "Physical";
    }
}

// Library class that manages members and books
class Library {
    private members: Member[] = [];
    private books: Book[] = [];

    addMember(member: Member): void {
        this.members.push(member);
    }

    addBook(book: Book): void {
        this.books.push(book);
    }
}

const myLibrary = new Library();

const alice = new Member("Alice");
const bob = new Member("Bob");

myLibrary.addMember(alice);
myLibrary.addMember(bob);

const digitalBook = new DigitalBook("The TypeScript Handbook");
const physicalBook = new PhysicalBook("Learning TypeScript Design Patterns");

myLibrary.addBook(digitalBook);
myLibrary.addBook(physicalBook);

alice.checkOutBook(digitalBook); // Prints: Alice checked out Digital book The TypeScript Handbook.
bob.checkOutBook(physicalBook);  // Prints: Bob checked out Physical book Learning TypeScript Design Patterns.

In this code snippet, Encapsulation is clearly demonstrated through the class structures and the controlled access to their attributes using TypeScript's private keyword. Polymorphism is vividly illustrated by how both DigitalBook and PhysicalBook classes inherit from the Book class but provide their own implementations of the getBookType method. This setup allows objects of DigitalBook and PhysicalBook to be used interchangeably when a book's type needs to be identified, demonstrating polymorphism's capability to work with objects of different classes through a common interface.

  • Encapsulation ensures that details about members and books are well-contained within their respective classes.
  • Polymorphism showcases flexibility by treating different book types uniformly, making the system more adaptive and scalable.
Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal