Thursday, January 22, 2026

Design Patterns

In software engineering, design patterns are tried‑and‑tested solutions to recurring problems in system design. They provide a shared vocabulary for developers and help create flexible, maintainable, and scalable applications. Broadly, design patterns fall into three categories: Creational, Structural, and Behavioral.

In brief, Design Patterns are Reusable Solutions for Common Software Problems.

Creational Patterns

Creational patterns deal with object creation mechanisms, making systems more independent of how their objects are created.

        1. Singleton 

    • Ensures a class has only one instance and provides a global point of access to it.
    • Example: Logger, configuration settings.
                    ```
                    // Ensures only one instance of a class exists.
// Useful for shared resources like loggers or configuration.
class Singleton {
    private static Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) instance = new Singleton();
        return instance;
    }
}

                    ``` 

        2. Factory Method  

    • Defines an interface for creating objects but lets subclasses decide which class to instantiate.
    • Example: Document creation in a word processor.
                    ```
                    // Defines an interface for creating objects, letting subclasses decide which type.
// Useful when you want flexible object creation.
interface Document { void open(); } 
class WordDoc implements Document { public void open(){ System.out.println("Word opened"); } }
class PdfDoc implements Document { public void open(){ System.out.println("PDF opened"); } }

class DocumentFactory {
    static Document create(String type) {
        return type.equals("word") ? new WordDoc() : new PdfDoc();
    }
}
                    ```

        3. Abstract Factory  

    • Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
    • Example: GUI toolkit that can create buttons and checkboxes.
                    ```
                    // Provides an interface for creating families of related objects.
// Example: GUI toolkit that can create platform-specific buttons.
interface Button { void render(); }
class WinButton implements Button { public void render(){ System.out.println("Windows Button"); } }
class MacButton implements Button { public void render(){ System.out.println("Mac Button"); } }

interface GUIFactory { Button createButton(); }
class WinFactory implements GUIFactory { public Button createButton(){ return new WinButton(); } }
class MacFactory implements GUIFactory { public Button createButton(){ return new MacButton(); } }
                    ```

        4. Builder  

    • Separates the construction of a complex object from its representation, allowing the same process to create different representations.
    • Example: Building a house with different layouts.
 ```
// Separates construction of a complex object from its representation.
// Useful for creating objects step by step.
class House {
    String walls, roof;
    public String toString(){ return walls + " & " + roof; }
}
class HouseBuilder {
    House house = new House();
    HouseBuilder buildWalls(){ house.walls="Brick Walls"; return this; }
    HouseBuilder buildRoof(){ house.roof="Tile Roof"; return this; }
    House build(){ return house; }
}
```

        5. Prototype  

    • Creates new objects by copying an existing prototype instance.
    • Example: Cloning objects.
```
// Creates new objects by cloning an existing prototype.
// Useful when object creation is expensive.
class Prototype implements Cloneable {
    String field;
    Prototype(String f){ field=f; }
    public Prototype clone(){ try { return (Prototype) super.clone(); } catch(Exception e){ return null; } }
}
```

Structural Patterns

Structural patterns focus on how classes and objects are composed to form larger structures.

        1. Adapter
    • Allows incompatible interfaces to work together by wrapping an existing class with a new interface.
    • Example: Connecting a legacy system to a modern application.
```
// Allows incompatible interfaces to work together.
// Wraps an old system with a new interface.
class OldSystem { void oldRequest(){ System.out.println("Old system"); } }
interface NewSystem { void request(); }
class Adapter implements NewSystem {
    OldSystem old = new OldSystem();
    public void request(){ old.oldRequest(); }
}
```
        2. Decorator  
    • Dynamically adds responsibilities to an object without modifying its structure.
    • Example: Adding scrollbars to a window.
```
// Dynamically adds responsibilities to an object.
// Example: adding scrollbars to a window.
interface Window { void draw(); }
class SimpleWindow implements Window { public void draw(){ System.out.println("Window"); } }
class ScrollDecorator implements Window {
    Window w; ScrollDecorator(Window w){ this.w=w; }
    public void draw(){ w.draw(); System.out.println(" + Scrollbars"); }
}
```
        3. Facade  
    • Provides a simplified interface to a complex subsystem.
    • Example: An API that simplifies the usage of a complex library.
```
// Provides a simplified interface to a complex subsystem.
// Example: hiding multiple steps behind one simple method.
class ComplexLib { void step1(){} void step2(){} }
class Facade {
    ComplexLib lib = new ComplexLib();
    void simple(){ lib.step1(); lib.step2(); System.out.println("Simplified"); }
}
```
        4. Proxy  
    • Provides a surrogate or placeholder for another object to control access to it.
    • Example: Lazy initialization, access control.
```
// Provides a placeholder to control access to another object.
// Useful for lazy loading or access control.
interface Service { void run(); }
class RealService implements Service { public void run(){ System.out.println("Running"); } }
class ProxyService implements Service {
    RealService real = new RealService();
    public void run(){ System.out.println("Proxy check"); real.run(); }
}
```

Behavioral Patterns

Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects.

        1. Observer  
    • Defines a one‑to‑many dependency so that when one object changes state, all dependents are notified automatically.
    • Example: Event handling systems.

      ```
                // Defines a one-to-many dependency so observers are notified of changes.
// Example: event handling systems.
import java.util.*;
interface Observer { void update(String msg); }
class Subject {
    List<Observer> obs = new ArrayList<>();
    void add(Observer o){ obs.add(o); }
    void notifyAllObs(){ for(Observer o:obs) o.update("Event happened"); }
}
```
        2. Strategy  
    • Defines a family of algorithms, encapsulates each one, and makes them interchangeable.
    • Example: Swappable sorting algorithms.
```
// Defines a family of algorithms and makes them interchangeable.
// Example: switching between sorting strategies.
interface Strategy { void execute(); }
class QuickSort implements Strategy { public void execute(){ System.out.println("QuickSort"); } }
class MergeSort implements Strategy { public void execute(){ System.out.println("MergeSort"); } }
class Context {
    Strategy s; Context(Strategy s){ this.s=s; }
    void run(){ s.execute(); }
}
```

        3. Command  
    • Encapsulates a request as an object, allowing for parameterization, queuing, and logging.
    • Example: Undo functionality in applications.
```
// Encapsulates a request as an object.
// Useful for undo/redo functionality.
interface Command { void execute(); }
class PrintCommand implements Command { public void execute(){ System.out.println("Print"); } }
class Invoker { Command c; Invoker(Command c){ this.c=c; } void run(){ c.execute(); } }
```
        4. Iterator  
    • Provides a way to access elements of a collection sequentially without exposing its internal representation.
    • Example: Iterating over a list of objects.
```
// Provides a way to access elements sequentially without exposing representation.
// Example: iterating over a collection.
import java.util.*;
class Demo {
    public static void main(String[] args){
        List<String> list = Arrays.asList("A","B","C");
        for(String s : list) System.out.println(s);
    }
}
```
        5. State  
    • Allows an object to alter its behavior when its internal state changes, appearing to change its class.
    • Example: State machines in game development.
```
// Allows an object to change behavior when its internal state changes.
// Example: state machines in games.
interface State { void handle(); }
class Happy implements State { public void handle(){ System.out.println("Happy"); } }
class Sad implements State { public void handle(){ System.out.println("Sad"); } }
class Context {
    State state;
    void set(State s){ state=s; }
    void request(){ state.handle(); }
}
```

Conclusion

Design patterns are not silver bullets, but they provide proven blueprints for solving common design challenges. By mastering these patterns, developers can write cleaner, more maintainable code and communicate solutions more effectively with their peers.

2 comments:

  1. Thanks for explaining things in simplified ways. It helped.

    ReplyDelete

Data Partitioning in System Design

Data Partitioning Techniques: Making Databases Scale Better As applications grow and data explodes, databases can become bottlenecks. Querie...