From grimoire
Introduces intermediary objects to decouple directly coupled components, improving testability and maintainability.
How this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:apply-indirectionThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Assign responsibility to an intermediate object to decouple two components that would otherwise depend directly on each other.
Assign responsibility to an intermediate object to decouple two components that would otherwise depend directly on each other.
Adopted by: The mechanism behind the majority of Gang of Four design patterns (Adapter, Facade, Proxy, Mediator, Broker, Bridge — all are applications of Indirection). David Wheeler's maxim — "Any problem in computer science can be solved by adding another layer of indirection" — is one of the most cited principles in software engineering. Dependency injection containers, service meshes, and API gateways are all industrial-scale applications of Indirection. Impact: Direct coupling between two volatile components means every change in one propagates to the other. An intermediate object isolates each side: component A only knows the intermediary's interface, and component B only knows the intermediary's interface. Changes on either side are absorbed by the intermediary. This is the mechanism that makes microservices independently deployable — the service mesh is the intermediary between every pair of services. Why best: Indirection is the general principle from which specific patterns emerge. When two classes are directly coupled and that coupling is causing problems, the solution is almost always to introduce an intermediary that owns the relationship. The choice of which intermediary pattern to use (Adapter, Facade, Proxy, Mediator) is determined by what kind of coupling is being broken.
Sources: Larman, "Applying UML and Patterns" (Prentice Hall, 2004); Gamma et al., "Design Patterns" (Addison-Wesley, 1994); Wheeler, "Computer Programming and Computer Systems" (Academic Press, 1963)
Not all direct coupling needs indirection. Add an intermediary only when the direct coupling creates a concrete problem.
Signals that indirection is warranted:
# Direct coupling — OrderService imports and instantiates Stripe directly
from stripe import StripeClient
class OrderService:
def charge(self, order):
client = StripeClient(api_key=STRIPE_KEY)
return client.charges.create(amount=order.total, currency="usd")
# Problem: can't test without network; can't swap payment processor
Define an interface that captures what component A needs from component B. Component A depends on the interface; component B implements it. The interface is the intermediary.
from abc import ABC, abstractmethod
# Intermediary: PaymentGateway interface
class PaymentGateway(ABC):
@abstractmethod
def charge(self, amount: float, currency: str) -> ChargeResult: ...
# B implements the interface
class StripeGateway(PaymentGateway):
def charge(self, amount, currency):
return StripeClient(STRIPE_KEY).charges.create(amount=amount, currency=currency)
# A depends on the interface only; knows nothing about Stripe
class OrderService:
def __init__(self, gateway: PaymentGateway):
self.gateway = gateway
def charge(self, order):
return self.gateway.charge(order.total, order.currency)
# Test injects a fake — no network needed
class FakeGateway(PaymentGateway):
def charge(self, amount, currency):
return ChargeResult(status="ok", charge_id="test-123")
| Problem | Intermediary pattern |
|---|---|
| Interface mismatch (API shapes differ) | Adapter |
| Complex subsystem with many classes | Facade |
| Adding behavior to object without modifying it | Proxy / Decorator |
| Many-to-many communication between components | Mediator |
| Abstracting remote/local call difference | Proxy |
| Shielding against interface volatility | Protected Variations |
When a high-level module depends on a low-level module (violating SOLID DIP), introduce an interface owned by the high-level module. The low-level module implements it. Dependency direction reverses.
# Bad — business logic (high-level) depends on MySQL (low-level)
class OrderService:
def find(self, id):
return MySQLConnection().query("SELECT * FROM orders WHERE id = ?", id)
# Good — interface owned by business layer; MySQLRepository is a low-level detail
class OrderRepository(ABC): # lives in business layer
@abstractmethod
def find(self, id) -> Order: ...
class MySQLOrderRepository(OrderRepository): # lives in infrastructure layer
def find(self, id):
return MySQLConnection().query("SELECT * FROM orders WHERE id = ?", id)
class OrderService:
def __init__(self, repo: OrderRepository): ... # high-level depends on abstraction
Every additional layer of indirection adds cognitive load. Two or three layers between a caller and the actual behavior is the practical limit before the code becomes harder to trace than the original coupling.
Rule: each layer of indirection must solve a specific, named coupling problem. If you cannot articulate what problem a layer solves, remove it.
Indirection for its own sake. Wrapping a stable, never-changing class in an interface just because "we might swap it later" adds complexity without reducing coupling. Apply indirection when coupling is causing a current problem or a highly likely future problem.
The wrong intermediary. Introducing a Mediator when an Adapter is needed, or a Facade when a Proxy is needed, creates the right structure but solves the wrong problem. Identify the coupling type first (Step 3), then choose the pattern.
Interface explosion. Creating an interface for every class produces a 1:1 mapping that provides no actual decoupling — callers still know exactly which implementation they will get via the interface name. Interfaces should represent roles, not types.
json.loads() in an interface adds no value; the library's API is stable and the implementation will never be swappednpx claudepluginhub jeffreytse/grimoire --plugin grimoireApplies low coupling principle when assigning responsibilities to classes. Detects and reduces excessive dependencies to improve testability and maintainability.
Applies Python design principles like KISS, SRP, composition over inheritance, and Rule of Three for designing services, refactoring monoliths, reducing coupling, and improving testability.
Designing loosely coupled code through dependency injection, reducing testability barriers and hidden dependencies.