Skip to main content

Definition

“Command pattern encapsulates a request as an object (Command), thereby letting you parameterize other objects (Receiver) with different requests, queue or log requests, and support undoable operations”
command

Explanation

You create a Command interface with an execute() method. Concrete command classes wrap a receiver and the action to perform (e.g., LightOnCommand holds a Light and its on() action). An invoker (e.g. a button or menu item) holds a command reference and calls execute() on it. Clients can pass commands to invokers at runtime. Because commands are objects, you can store, queue or undo them.

Code

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass, field


# ===== Products (can be totally different types) =====

@dataclass
class Product1:
    name: str = "Product1"
    features: list[str] = field(default_factory=list)

    def __str__(self) -> str:
        return f"{self.name}({', '.join(self.features) or 'no features'})"


@dataclass
class Product2:
    name: str = "Product2"
    parts: list[str] = field(default_factory=list)

    def __str__(self) -> str:
        return f"{self.name}({', '.join(self.parts) or 'no parts'})"


# ===== Builder interface (per the UML) =====

class Builder(ABC):
    @abstractmethod
    def reset(self) -> None: ...
    @abstractmethod
    def buildStepA(self) -> None: ...
    @abstractmethod
    def buildStepB(self) -> None: ...
    @abstractmethod
    def buildStepZ(self) -> None: ...
    @abstractmethod
    def getResult(self):
        """Return the product and (optionally) prepare for a new build."""


# ===== Concrete builders (each yields a different product) =====

class ConcreteBuilder1(Builder):
    def __init__(self) -> None:
        self._product: Product1 | None = None
        self.reset()

    def reset(self) -> None:
        self._product = Product1()

    def buildStepA(self) -> None:
        self._product.features.append("FeatureA1")

    def buildStepB(self) -> None:
        self._product.features.append("FeatureB1")

    def buildStepZ(self) -> None:
        self._product.features.append("FeatureZ1")

    def getResult(self) -> Product1:
        # Typical builder behavior: hand off the current product and reset
        product = self._product
        self.reset()
        return product


class ConcreteBuilder2(Builder):
    def __init__(self) -> None:
        self._product: Product2 | None = None
        self.reset()

    def reset(self) -> None:
        self._product = Product2()

    def buildStepA(self) -> None:
        self._product.parts.append("PartA2")

    def buildStepB(self) -> None:
        self._product.parts.append("PartB2")

    def buildStepZ(self) -> None:
        self._product.parts.append("PartZ2")

    def getResult(self) -> Product2:
        product = self._product
        self.reset()
        return product


# ===== Director (defines build order / reusable recipes) =====

class Director:
    def __init__(self, builder: Builder) -> None:
        self.builder = builder

    def changeBuilder(self, builder: Builder) -> None:
        self.builder = builder

    def make(self, type: str = "simple") -> None:
        """
        Reusable 'recipes' that decide which steps to call and in what order.
        The Director does NOT return the product; the client asks the builder.
        """
        self.builder.reset()
        if type == "simple":
            self.builder.buildStepA()
        elif type == "standard":
            self.builder.buildStepA()
            self.builder.buildStepB()
        elif type == "deluxe":
            self.builder.buildStepB()
            self.builder.buildStepZ()
        elif type == "ultimate":
            self.builder.buildStepA()
            self.builder.buildStepB()
            self.builder.buildStepZ()
        else:
            raise ValueError(f"Unknown build type: {type}")


# ===== Client code (matches the call sequence in the diagram) =====

if __name__ == "__main__":
    # b = new ConcreteBuilder1()
    b = ConcreteBuilder1()
    # d = new Director(b)
    d = Director(b)
    # d.make()
    d.make("ultimate")
    # Product1 p = b.getResult()
    p1 = b.getResult()
    print(p1)  # -> Product1(FeatureA1, FeatureB1, FeatureZ1)

    # Switch builder at runtime and reuse the same Director "recipes"
    d.changeBuilder(ConcreteBuilder2())
    d.make("simple")
    p2 = d.builder.getResult()  # or keep a reference to the builder instance
    print(p2)  # -> Product2(PartA2)


Analogy

Building a house or assembling a custom computer: the builder can produce different models (modern vs medieval house) by changing how each part is built, while the construction steps (floor, walls, roof) are the same. Or a meal order at a fast-food restaurant: a standard meal builder picks burger, drink, sides; you can create different meals by choosing different implementations of builder (veg meal vs chicken meal).

Interview Insights

Common uses: When an object has many optional parts or complex construction (e.g. creating documents with optional sections, building HTML with various tags, configuring complex objects in tests). Often used for toString() or parsing, or in Fluent APIs.
Advantages: Encapsulates construction code; improves readability. Supports step-by-step construction and validation. Isolates the creation of different representations of an object.
Disadvantages: More classes/code overhead. Can be overkill if object construction is simple.\