Understanding the Design Patterns Used in The Leola Programming Language

class DatabaseConnection { private static instance: DatabaseConnection; private constructor() { } public static getInstance(): DatabaseConnection { if (!DatabaseConnection.instance) { DatabaseConnection.instance = new DatabaseConnection(); } return DatabaseConnection.instance; } // Other methods and properties... } abstract class Animal { public abstract makeSound(): void; } class Dog extends Animal { public makeSound(): void { console.log("Woof!"); } } class Cat extends Animal { public makeSound(): void { console.log("Meow!"); } } class AnimalFactory { public static createAnimal(type: string): Animal { switch (type) { case "dog": return new Dog(); case "cat": return new Cat(); default: throw new Error("Invalid animal type."); } } } // Usage let dog: Animal = AnimalFactory.createAnimal("dog"); let cat: Animal = AnimalFactory.createAnimal("cat"); dog.makeSound(); // Output: Woof! cat.makeSound(); // Output: Meow! interface Observer { notify(data: any): void; } class Subject { private observers: Observer[] = []; public addObserver(observer: Observer): void { this.observers.push(observer); } public removeObserver(observer: Observer): void { const index = this.observers.indexOf(observer); if (index !== -1) { this.observers.splice(index, 1); } } public notifyObservers(data: any): void { for (const observer of this.observers) { observer.notify(data); } } } class ConcreteObserver implements Observer { public notify(data: any): void { console.log("Received data:", data); } } // Usage const subject = new Subject(); const observer1 = new ConcreteObserver(); const observer2 = new ConcreteObserver(); subject.addObserver(observer1); subject.addObserver(observer2); subject.notifyObservers("Hello, observers!"); // Output: // Received data: Hello, observers! // Received data: Hello, observers!