Design Patterns, Explained Simply · Modern Patterns and Choosing Well

Anti-Patterns and Over-Engineering

The most common ways patterns go wrong: god objects, singletons as global variables, service locators, premature abstraction and using a favourite pattern for everything.

The problem

A team discovers design patterns and starts using them everywhere. A greeting feature ends up with GreetingStrategy, GreetingStrategyFactory, AbstractGreetingProvider and a GreetingService registered in a container: five files to print "Hello, Ada". Meanwhile the real problem in the codebase, a 3,000-line OrderManager that does everything, is untouched.

Patterns are tools. Used without the problem they solve, they make code harder to read.

Real-world analogy

Building a garden shed with the blueprint for a skyscraper: steel frame, elevator shaft, fire stairs. Every part is "best practice" for skyscrapers. For a shed it is wasted effort and a worse result. Good architects pick the structure that fits the building.

The idea

An anti-pattern is a common response to a recurring problem that looks reasonable but usually makes things worse. The ones you will meet most:

Anti-pattern What it looks like Better
God object One class (OrderManager, Utils) that knows and does everything Split by responsibility (SRP); Facade for a simple entry point
Singleton as global variable Config.instance() called from everywhere One instance created at startup and injected
Service locator container.get("mailer") inside business classes Constructor injection of the specific dependencies
Golden hammer Every problem solved with the team's favourite pattern Start from the problem, not the pattern
Speculative generality Interfaces, factories and hooks "in case we need them" YAGNI: add the abstraction when the second case arrives
Anemic domain model Objects with only getters and setters; all logic in "services" Put behaviour with the data it uses, where it helps
Copy-paste programming The same logic in five places with small differences Extract a function; Template Method or Strategy if variants differ

Two rules of thumb help:

  • YAGNI ("you aren't gonna need it"): don't build for imagined requirements.
  • Rule of three: duplicate once if you must; on the third occurrence, extract the abstraction. By then you know what actually varies.

The code

The same output, written twice: an over-engineered version full of patterns, and the plain version that should have been written. Patterns are justified only when the problem they solve exists.

Five pattern roles for a greeting, and the plain versionTry it yourself
# Over-engineered: strategy + factory + abstract provider + service, for one greeting
class GreetingStrategy:
    def greet(self, name):
        raise NotImplementedError


class FriendlyGreeting(GreetingStrategy):
    def greet(self, name):
        return f"Hello, {name}!"


class GreetingStrategyFactory:
    def create(self, kind):
        if kind == "friendly":
            return FriendlyGreeting()
        raise ValueError(kind)


class GreetingService:
    def __init__(self, factory):
        self.factory = factory

    def greet(self, name, kind="friendly"):
        return self.factory.create(kind).greet(name)


print(GreetingService(GreetingStrategyFactory()).greet("Ada"))


# Plain: what the feature actually needs today
def greet(name):
    return f"Hello, {name}!"


print(greet("Ada"))
print("classes in version 1: 4 | in version 2: 0 | same behaviour:",
      GreetingService(GreetingStrategyFactory()).greet("Bo") == greet("Bo"))

Output

Hello, Ada!
Hello, Ada!
classes in version 1: 4 | in version 2: 0 | same behaviour: True

If greetings later need five styles chosen per customer, refactor the function into a Strategy then. The pattern will fit the real variation instead of a guessed one.

Common mistakes

  • Treating patterns as a checklist ("our codebase should use all 23").
  • Naming without the pattern: a class called UserFactory that doesn't create anything, or an Adapter that is really a Facade. Wrong names mislead readers.
  • Refactoring toward patterns without tests. Changing structure safely needs tests that pin the behaviour first.
  • Ignoring the language. Many patterns shrink to one line in languages with first-class functions (Strategy, Command, Observer as callbacks), and some disappear (Iterator via generators, Singleton via modules).

Key points

  • An anti-pattern is a common solution that looks right but causes more problems than it solves.
  • The usual ones: god object, singleton-as-global, service locator, golden hammer, speculative generality.
  • Apply YAGNI and the rule of three: abstract when the variation is real.
  • The goal is simple, changeable code. Patterns are a means, not the goal.
  • Further reading: God object and Martin Fowler on service locators.

Practice questions

What is an anti-pattern? Give three examples.

Answer

A common response to a recurring problem that seems reasonable but usually makes things worse. Examples: the god object (one class doing everything), singleton as a global variable, service locator hiding dependencies, the golden hammer (one favourite solution for everything), and speculative generality.

What do YAGNI and the rule of three mean for applying design patterns?

Answer

YAGNI (you aren't gonna need it): don't add abstractions for imagined future needs. Rule of three: tolerate a little duplication, and when the third similar case appears, extract the abstraction, because by then you know what actually varies.

How would you refactor a god object?

Answer

Add tests that pin its current behaviour, then identify responsibilities that change for different reasons and extract them into focused classes (SRP), injecting them back. A facade can keep a simple entry point for existing callers while the internals are split.

Open in the app to mark lessons complete, take notes and track your progress.