System Design LAC

website·Indexed 29 days ago

https://system-design-lac.vercel.app

A comprehensive guide to software design principles and patterns, featuring detailed explanations and examples of SOLID principles, creational, structural, and behavioral design patterns implemented in Java and TypeScript.

Tokens
14.9K
Snippets
26
Records
58
Agent score
74%

What's inside System Design LAC

  1. Overview of Creational Design Patterns

    Creational design patterns focus on object creation mechanisms, providing ways to instantiate objects while decoupling the client code from the specific classes being created. This increases flexibility and reusability in software development. Common creational patterns include: Singleton, Factory Method, Abstract Factory Method, Builder, Prototype, and Object Pool.
  2. Overview of Creational Design Patterns

    Creational design patterns provide mechanisms to create objects in a manner suitable for a given situation. Their primary goal is to decouple client code from the specific classes or objects being instantiated, thereby increasing flexibility and reusability in software development. Common creational patterns include:

    1. Singleton Pattern
    2. Factory Method Pattern
    3. Abstract Factory Method
    4. Builder Pattern
    5. Prototype Pattern
    6. Object Pool
  3. Compare Template Method pattern vs Polymorphism

    While both involve inheritance, the Template Method pattern is a higher-level design structure specifically for organizing algorithms, whereas polymorphism is a fundamental OOP concept for object behavior.
    CategoryTemplate MethodPolymorphism
    PurposeDefines algorithm structure; subclasses provide specific step implementations.Treats different classes as a common superclass/interface for different behavior.
    Design LevelHigh-level design pattern for organizing algorithms.Fundamental OOP concept focusing on object behavior.
    AbstractionHigher levelLower level
    ImplementationAbstract or virtual methodsInheritance or interfaces
  4. Understand Liskov's Substitution Principle (LSP)

    Liskov's Substitution Principle (LSP) states that objects of a superclass should be substitutable with objects of its subclasses without affecting the correctness of the program. If a program is designed to work with a certain type, it should also work correctly with any subtype of that type. Formally: if a property q(x) is provable about objects x of type T, then q(y) should be provable for objects y of type S, where S is a subtype of T.
  5. Implement the State behavioral design pattern

    The State pattern allows an object to alter its behavior when its internal state changes, making it appear as if the object changed its class. This is particularly useful when an object's behavior depends on its internal state and needs to change dynamically at runtime without using complex conditional statements.

    Key components of the pattern:

    • Context: The object whose behavior changes. It maintains a reference to the current state and delegates behavior to it.
    • State: An interface or abstract class defining common methods for all concrete states.
    • Concrete State: Specific implementations of the State interface, each providing behavior unique to that state.
  6. Implement the Observer design pattern

    The Observer pattern is a behavioral design pattern used to define a subscription mechanism to notify multiple objects (observers) about events happening to the object they are observing (the subject). It establishes a one-to-many dependency so that when the subject's state changes, all registered dependents are notified and updated automatically. This is also known as the Publisher-Subscriber model.

    Key characteristics:

    • Subscribers can be added or removed in real-time.
    • Subscribers do not need to be of the same type.
    • Common use cases include event listeners in GUI frameworks, reactive programming, and notification systems (e.g., social media followers or YouTube subscriptions).
  7. Implement the Command behavioral design pattern

    The Command pattern is a behavioral design pattern that encapsulates a request as a stand-alone object. This decoupling allows for parameterizing clients with different requests, queuing or logging requests, and supporting undoable operations. It is particularly useful in multithreaded environments.

    Key Components

    • Command: An interface that defines the method for executing an operation (e.g., executeCommand()).
    • Concrete Command: Implements the Command interface; it binds the receiver object to the action to be performed.
    • Receiver: The object that contains the actual business logic to perform the work.
    • Invoker: The object that triggers the command. It does not need to know the internal logic of the receiver, only how to call the command's execution method.
    • Client: The object that creates the Concrete Command and associates it with a Receiver, then passes the command to the Invoker.
  8. Implement the Mediator Pattern to reduce object dependencies

    The Mediator pattern is a behavioral design pattern used to reduce chaotic dependencies between objects by restricting direct communication. Instead of objects referring to each other explicitly, they collaborate only via a mediator object, promoting loose coupling and allowing interaction logic to vary independently.

    Key Components

    • Mediator: An interface that defines how participating objects (Colleagues) communicate.
    • ConcreteMediator: Implements the Mediator interface and coordinates communication among Colleague objects, maintaining a list of registered participants.
    • Colleague: An abstract class or interface for communication with other colleagues.
    • Concrete Colleague: Specific implementations that send and receive messages via the Mediator. They are typically unaware of other concrete colleagues.
  9. Implement the Factory Method Pattern

    The Factory Method pattern delegates the creation of objects to subclasses. It is used when objects share a common characteristic or hierarchy (e.g., different types of HTML elements) and you want to avoid using the new operator manually. Instead, a factory method (typically called create) abstracts the instantiation logic and returns an object conforming to a common interface.
  10. Implement 'Has-a' relationships via Composition

    Composition is a strong 'has-a' relationship where a parent class is composed of one or more objects of another class. The lifetime of the composed objects is tightly coupled to the parent; if the parent object is destroyed, the composed objects are also destroyed. Composed objects cannot exist independently.
    public class Car {
        private Engine engine;
    
        public Car() {
            // The Engine is created inside the Car, coupling their lifecycles
            engine = new Engine();
        }
    }
  11. Implement the Prototype Pattern in TypeScript

    The Prototype Pattern is used to create new objects by copying an existing prototypical instance. This avoids the overhead of manual object creation and property assignment, especially when objects need to be cloned at runtime or when the new operator is too expensive.

    Key implementation steps:

    1. Define a Prototype interface that declares a clone() method.
    2. Implement this interface in concrete classes (e.g., game characters like Soldiers).
    3. Use the clone() method to produce identical copies of an object, then modify specific properties as needed.
    interface Prototype {
      clone(): Prototype;
    }
    
    class Soldier implements Prototype {
      constructor(public weapon: string, public rank: string) {}
    
      clone(): Prototype {
        return new Soldier(this.weapon, this.rank);
      }
    }
    
    // Usage
    const basicSoldier = new Soldier('Rifle', 'Private');
    const clonedSoldier = basicSoldier.clone() as Soldier;
    clonedSoldier.rank = 'Sergeant'; // Modify properties after cloning
  12. Implement the Composite Design Pattern in Java

    The Composite pattern is a structural design pattern used to compose objects into tree structures to represent part-whole hierarchies. It allows clients to treat individual objects (leaves) and compositions of objects (composites) uniformly through a common interface.

    Key Components:

    • Component: An interface or abstract class defining common methods for all objects in the composition (e.g., FileSystemComponent).
    • Leaf: An individual object that implements the Component interface and represents the 'part' (e.g., File).
    • Composite: A container that holds other components (both leaves and other composites) and implements the Component interface, representing the 'whole' (e.g., Directory).
    // Component: Common interface for both leaf and composite objects
    interface FileSystemComponent {
        void showDetails();
    }
    
    // Leaf: Represents individual objects
    class File implements FileSystemComponent {
        private String name;
    
        public File(String name) {
            this.name = name;
        }
    
        public void showDetails() {
            System.out.println("File: " + name);
        }
    }
    
    // Composite: Represents containers that can hold other components
    class Directory implements FileSystemComponent {
        private String name;
        private List<FileSystemComponent> components;
    
        public Directory(String name) {
            this.name = name;
            components = new ArrayList<>();
        }
    
        public void addComponent(FileSystemComponent component) {
            components.add(component);
        }
    
        public void removeComponent(FileSystemComponent component) {
            components.remove(component);
        }
    
        public void showDetails() {
            System.out.println("Directory: " + name);
            for (FileSystemComponent component : components) {
                component.showDetails();
            }
        }
    }
    
    // Usage
    public class CompositeExample {
        public static void main(String[] args) {
            FileSystemComponent file1 = new File("document.txt");
            FileSystemComponent file2 = new File("image.jpg");
            FileSystemComponent subdirectory = new Directory("documents");
            FileSystemComponent directory = new Directory("root");
    
            ((Directory) subdirectory).addComponent(file1);
            ((Directory) directory).addComponent(file2);
            ((Directory) directory).addComponent(subdirectory);
    
            directory.showDetails();
        }
    }