SkillByAIOpen interactive version →

Lesson 3 / 25

When Patterns Help and When They Hurt

Patterns solve problems; they are not goals.

Pull patterns in, do not push them

Patterns help when you actually have the problem they solve: several interchangeable algorithms (Strategy), an incompatible third-party API (Adapter), a complex object with many optional parts (Builder). They hurt when added speculatively: an interface with one implementation, a factory that only ever creates one class, or five layers of indirection for a CRUD screen. This is premature abstraction; it makes code harder to read and change. A good habit is to write the simple version first, and refactor towards a pattern when duplication or change requests show you where the flexibility is needed. Also note that many GoF patterns compensate for missing language features; in languages with first-class functions, closures and modules, some patterns shrink to a few lines.

Over-engineered versus enough

The same tax calculation.

// Over-engineered: one rule, three abstractions
interface TaxStrategy { calculate(amount: number): number; }
class StandardTax implements TaxStrategy {
  calculate(amount: number) { return amount * 0.2; }
}
class TaxStrategyFactory {
  create(): TaxStrategy { return new StandardTax(); }
}
const tax = new TaxStrategyFactory().create().calculate(100);

// Enough, until a second tax rule actually appears
const TAX_RATE = 0.2;
const calculateTax = (amount: number) => amount * TAX_RATE;

The rule of three

A common heuristic: tolerate duplication the first two times, and extract an abstraction when the third similar case appears and you can see what really varies.

Quick check: Which situation is most likely premature abstraction?

  • A Builder for an object with many optional settings
  • An Adapter wrapping an incompatible payment SDK
  • A Strategy for three real shipping-cost rules
  • An interface and factory for a class that has only one implementation and no planned variants
Answer

An interface and factory for a class that has only one implementation and no planned variants — Abstractions should follow real variation.