This repository contains notes and examples related to Low-Level Design (LLD) and various Design Patterns. The goal is to provide concise, revision-friendly material for understanding and applying common software design solutions.
Design patterns are formalized best practices that provide typical solutions to common problems in software design. They are categorized into three main types:
These patterns deal with object creation mechanisms, aiming to increase flexibility and reuse of existing code while controlling the object creation process.
- Builder: Separates the construction of a complex object from its representation. Use when an object has many optional parameters or requires a step-by-step construction. (e.g., configuring a complex
Burgerwith various toppings). - Factory Method: Defines an interface for creating an object, but lets subclasses decide which class to instantiate. Use when a class can't anticipate the class of objects it must create. (e.g., creating different types of
Vehicleobjects based on input). - Abstract Factory: Provides an interface for creating families of related or dependent objects without specifying their concrete classes. Use when your system needs to be independent of how its products are created, composed, and represented. (e.g., creating UI components for different operating systems).
- Singleton: Ensures a class has only one instance and provides a global point of access to it. Use when exactly one instance of a class is required to coordinate actions across the system. (e.g., a
LoggerorConfigurationManager). - Prototype: Specifies the kinds of objects to create using a prototypical instance, and creates new objects by copying this prototype. Use when creating an object is expensive or complex, and you need to create many similar objects. (e.g., cloning a pre-configured
Documentobject).
These patterns concern class and object composition, describing how to assemble objects and classes into larger structures while keeping these structures flexible and efficient.
- Adapter: Allows objects with incompatible interfaces to collaborate. Use when you want to use an existing class, but its interface doesn't match the one you need. (e.g., integrating a
LegacyLoggerwith aNewLoggerinterface). - Bridge: Decouples an abstraction from its implementation so that the two can vary independently. Use when you want to avoid a permanent binding between an abstraction and its implementation, allowing both to evolve independently. (e.g.,
ShapeandColorcan vary without class explosion). - Composite: Composes objects into tree structures to represent part-whole hierarchies, letting clients treat individual objects and compositions uniformly. Use when you want to represent part-whole hierarchies and clients should ignore the difference between individual objects and compositions. (e.g.,
FilesandFoldersin a file system). - Decorator: Attaches new responsibilities to objects dynamically. Use when you need to add responsibilities to individual objects dynamically and transparently, without affecting other objects. (e.g., adding
Milk,Sugarto aCoffee). - Facade: Provides a simplified interface to a complex subsystem. Use when you want to provide a simple, unified interface to a complex set of classes. (e.g., a
HomeTheaterFacadeto control multiple entertainment devices). - Flyweight: Lets you fit more objects into the available RAM by sharing common parts of state between multiple objects. Use when you have a large number of fine-grained objects that share common state. (e.g., character objects in a text editor).
- Proxy: Provides a surrogate or placeholder for another object to control access to it. Use when you need to control, manage, or enhance access to a single object. (e.g.,
LazyImageProxyfor expensive image loading,BankAccountProxyfor access control).
These patterns are concerned with algorithms and the assignment of responsibilities between objects, describing how objects and classes interact and distribute responsibility.
- Strategy: Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Use when an object needs to perform a task using one of several algorithms, and you want to choose the algorithm at runtime. (e.g., different
PaymentStrategyfor aShoppingCart). - Chain of Responsibility: Passes requests along a chain of handlers. Use when you want to decouple the sender of a request from its receiver, and multiple objects can handle the request. (e.g.,
ApprovalProcesswhere requests go through different levels of managers). - Command: Turns a request into a stand-alone object that contains all information about the request. Use when you want to parameterize objects with operations, queue operations, or support undoable operations. (e.g.,
MenuItemobjects invokingCommandobjects). - Iterator: Provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation. Use when you need to traverse elements of a collection without exposing its internal structure. (e.g., iterating through a
ListorTree). - Mediator: Reduces chaotic dependencies between objects by centralizing communication through a mediator object. Use when a set of objects communicate in complex but well-defined ways, and you want to avoid direct coupling between them. (e.g.,
ChatRoommediating communication betweenUsers). - Memento: Lets you save and restore the previous state of an object without revealing the details of its implementation. Use when you need to capture and externalize an object's internal state so that the object can be restored to this state later. (e.g.,
Undo/Redofunctionality in an editor). - Observer: Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. Use when a change in one object requires changing others, and you don't know how many objects need to be changed. (e.g.,
StockMarketnotifyingInvestorsof price changes). - State: Lets an object alter its behavior when its internal state changes. The object will appear to change its class. Use when an object's behavior depends on its state, and it must change its behavior at runtime depending on that state. (e.g.,
TrafficLightchanging behavior based onRed,Yellow,Greenstates). - Template Method: Defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure. Use when you want to implement the invariant parts of an algorithm once and let subclasses implement the behavior that can vary. (e.g.,
BuildHousetemplate with steps likebuildWalls,buildRoof). - Visitor: Lets you separate algorithms from the objects on which they operate. Use when you need to perform an operation on all elements of an object structure, and you want to add new operations without modifying the element classes. (e.g.,
ExportVisitorto export differentShapeobjects to XML/JSON).