🎁 Get the FREE AI Skills Starter Guide β€” Subscribe β†’
BytesAgainBytesAgain
πŸ¦€ ClawHub

Strategy Pattern Implementor

by @quochungto

Implement the Strategy pattern to encapsulate a family of interchangeable algorithms behind a common interface. Use when you have multiple conditional branch...

⚑ When to Use
TriggerAction
- A class contains `switch` statements or `if/else` chains that select between behavioral variants β€” **the primary code smell**
- Multiple related classes differ only in their behavior (same structure, different computation)
- You need different space-time trade-offs for the same operation (fast-and-approximate vs. slow-and-precise)
- An algorithm uses data clients should not know about (complex internal data structures that would leak through the interface)
- You need to swap the algorithm at runtime without changing the client
Before starting, confirm:
- Is this a **client-selected** algorithm switch (Strategy) rather than an **object self-transitioning** based on internal state (State)? If unsure, use `behavioral-pattern-selector` first.
- Does the variation belong in the class itself, or is it better expressed via a superclass skeleton with subclass steps (Template Method via inheritance)? Strategy uses composition β€” the algorithm is a separate object, not a subclass.
---
πŸ’‘ Examples

Example 1: Payment Processing (Classic Conditional to Strategy)

Scenario: An e-commerce checkout has a PaymentService with a large if/else chain selecting between credit card, PayPal, and bank transfer logic. A new payment method is requested every quarter, and each addition requires modifying PaymentService directly.

Trigger: "Every time we add a payment provider, we touch the same process_payment method and it keeps growing."

Before (the code smell):

class PaymentService:
    def process_payment(self, amount: float, method: str, details: dict):
        if method == "credit_card":
            # validate card, charge via Stripe, handle 3DS...
        elif method == "paypal":
            # OAuth flow, PayPal API call, webhook...
        elif method == "bank_transfer":
            # IBAN validation, SEPA/ACH routing...
        else:
            raise ValueError(f"Unknown method: {method}")

After (Strategy):

class PaymentStrategy:
    def process(self, amount: float, details: dict) -> PaymentResult:
        raise NotImplementedError

class CreditCardStrategy(PaymentStrategy): def process(self, amount: float, details: dict) -> PaymentResult: # Stripe integration, 3DS, card validation

class PayPalStrategy(PaymentStrategy): def process(self, amount: float, details: dict) -> PaymentResult: # OAuth, PayPal API, webhook handling

class BankTransferStrategy(PaymentStrategy): def process(self, amount: float, details: dict) -> PaymentResult: # IBAN validation, SEPA/ACH routing

class PaymentService: def __init__(self, strategy: PaymentStrategy): self._strategy = strategy

def process_payment(self, amount: float, details: dict) -> PaymentResult: return self._strategy.process(amount, details)

Caller selects the strategy β€” PaymentService never changes

service = PaymentService(CreditCardStrategy()) result = service.process_payment(99.99, {"card_number": "..."})

Output: PaymentService is closed to modification. Adding CryptoStrategy requires zero changes to existing code.


Example 2: Lexi Document Formatter (GoF Case Study)

Scenario: Lexi, a WYSIWYG document editor, must break text into lines. Several algorithms exist: a simple greedy line-breaker, a full TeX-quality algorithm optimizing paragraph-level color, and a fixed-interval breaker for icon grids. The algorithms have different speed-quality trade-offs and the user may switch between them.

Trigger: "The formatting algorithm needs to change at runtime depending on document type, and we need to add new algorithms without touching the document structure."

Design (from GoF, pages 48-50 and 296-298):

Context:     Composition  (holds content glyphs, line width)
Strategy:    Compositor   (abstract β€” Compose() interface)
ConcreteA:   SimpleCompositor    β€” greedy, line-at-a-time, fast
ConcreteB:   TeXCompositor       β€” paragraph-at-a-time, optimizes whitespace color
ConcreteC:   ArrayCompositor     β€” fixed interval, for icon grids

The Compositor interface takes all layout data as explicit parameters (Approach 1 β€” data to the strategy):

virtual int Compose(
    Coord natural[], Coord stretch[], Coord shrink[],
    int componentCount, int lineWidth, int breaks[]
) = 0;

This interface is wide enough to support all three algorithms. SimpleCompositor ignores stretchability; ArrayCompositor ignores everything except component count. This is the communication overhead trade-off β€” some ConcreteStrategies receive data they do not use. The GoF accept this because the alternative (passing this as a context reference) would couple Compositor subclasses to Composition's full interface.

Runtime swap:

// Switching quality at runtime β€” no reconstruction of Composition
composition->SetCompositor(new TeXCompositor());
composition->Repair();  // delegates to compositor->Compose(...)

Output: Adding a new linebreaking algorithm is a new Compositor subclass. Neither Composition nor the glyph classes are ever modified.


Example 3: Report Exporter with Runtime Selection

Scenario: A reporting tool exports data as CSV, JSON, or PDF. The format is determined by user selection at runtime. Currently the export logic lives in a single export() method with a format string parameter driving a conditional.

Trigger: "Users can choose the export format from a dropdown β€” the format isn't known until they click Export."

Strategy interface (data to strategy, Approach 1):

class ExportStrategy:
    def export(self, records: list[dict], output_path: str) -> None:
        raise NotImplementedError

class CsvExportStrategy(ExportStrategy): def export(self, records, output_path): import csv with open(output_path, "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=records[0].keys()) writer.writeheader() writer.writerows(records)

class JsonExportStrategy(ExportStrategy): def export(self, records, output_path): import json with open(output_path, "w") as f: json.dump(records, f, indent=2)

class PdfExportStrategy(ExportStrategy): def export(self, records, output_path): # reportlab or weasyprint integration ...

class ReportExporter: _STRATEGIES = { "csv": CsvExportStrategy, "json": JsonExportStrategy, "pdf": PdfExportStrategy, }

def __init__(self): self._strategy: ExportStrategy = CsvExportStrategy() # default

def set_format(self, format_key: str) -> None: """Runtime swap β€” called when user selects format in UI.""" cls = self._STRATEGIES.get(format_key) if not cls: raise ValueError(f"Unknown format: {format_key}") self._strategy = cls()

def export(self, records: list[dict], output_path: str) -> None: self._strategy.export(records, output_path)

Key decision: The _STRATEGIES registry centralizes selection logic in the Context, keeping callers simple. An alternative is to move this registry to a factory, which is preferable when selection logic becomes complex (e.g., involving feature flags or user tier).

Output: The UI dropdown maps to set_format(). Adding XML export is a new XmlExportStrategy class plus one entry in _STRATEGIES β€” the rest of the system is unchanged.


View on ClawHub
TERMINAL
clawhub install bookforge-strategy-pattern-implementor

πŸ§ͺ Use this skill with your agent

Most visitors already have an agent. Pick your environment, install or copy the workflow, then run the smoke-test prompt above.

πŸ” Can't find the right skill?

Search 60,000+ AI agent skills β€” free, no login needed.

Search Skills β†’