Skip to content

SOLID Anti-Patterns

When to Recognize Violations

During code review, watch for these anti-patterns that violate SOLID principles and cause maintenance pain.

Anti-Pattern: God Object (SRP Violation)

Description: One class doing everything.

Example (PHP):

class Application {
    public function authenticateUser($username, $password) { /* ... */ }
    public function renderPage($template, $data) { /* ... */ }
    public function saveToDatabase($entity) { /* ... */ }
    public function sendEmail($to, $message) { /* ... */ }
    public function processPayment($amount) { /* ... */ }
    public function generatePDF($data) { /* ... */ }
}

Why it's bad: - Changes for auth, rendering, database, email, payments, PDF generation - Impossible to test in isolation (mocking nightmare) - Team members conflict on same file - Unclear ownership

Fix: Split by responsibility (AuthService, ViewRenderer, DatabaseRepository, etc.)

Anti-Pattern: Modification Cascade (OCP Violation)

Description: Adding a feature requires modifying many existing classes.

Example (TypeScript):

class ShapeDrawer {
  draw(shape: any) {
    if (shape.type === 'circle') {
      // draw circle
    } else if (shape.type === 'square') {
      // draw square
    } else if (shape.type === 'triangle') {
      // draw triangle
    }
    // Adding hexagon requires modifying this method
  }
}

class ShapeAreaCalculator {
  calculate(shape: any) {
    if (shape.type === 'circle') {
      return Math.PI * shape.radius ** 2;
    } else if (shape.type === 'square') {
      return shape.side ** 2;
    }
    // Adding hexagon requires modifying this too
  }
}

Why it's bad: - Adding one shape type requires changing multiple files - Risk of missing a spot (incomplete implementation) - Can't add shapes without source code access (closed source libraries)

Fix: Polymorphism -- each shape implements draw() and area().

Anti-Pattern: Refused Bequest (LSP Violation)

Description: Subclass doesn't honor parent's contract.

Example (Python):

class Vehicle:
    def start_engine(self):
        return "Engine started"

    def drive(self):
        return "Driving"

class Bicycle(Vehicle):
    def start_engine(self):
        raise NotImplementedError("Bicycles don't have engines") # LSP VIOLATION

    def drive(self):
        return "Pedaling"

Why it's bad: - Code expecting Vehicle breaks with Bicycle - NotImplementedError at runtime (should be compile-time error) - Violates "is-a" relationship (bicycle isn't a vehicle with an engine)

Fix: Separate hierarchies (EngineVehicle vs HumanPoweredVehicle).

Anti-Pattern: Interface Bloat (ISP Violation)

Description: Forcing implementers to implement methods they don't use.

Example (Java):

interface Employee {
    void work();
    void attendMeeting();
    void submitTimesheet();
    void approveExpenses(); // Only managers do this
    void conductInterview(); // Only HR does this
}

class Developer implements Employee {
    public void work() { /* code */ }
    public void attendMeeting() { /* attend */ }
    public void submitTimesheet() { /* submit */ }
    public void approveExpenses() { throw new UnsupportedOperationException(); } // ISP VIOLATION
    public void conductInterview() { throw new UnsupportedOperationException(); } // ISP VIOLATION
}

Why it's bad: - Developers forced to implement manager/HR methods - Runtime exceptions instead of compile-time safety - Changes to manager methods affect all employees

Fix: Role interfaces (IWorker, IManager, IInterviewer).

Anti-Pattern: Service Locator (DIP Violation)

Description: Using a global registry to fetch dependencies.

Example (C#):

class OrderService {
    public void ProcessOrder(Order order) {
        var repository = ServiceLocator.Get<IOrderRepository>(); // DIP VIOLATION
        var emailService = ServiceLocator.Get<IEmailService>(); // DIP VIOLATION

        repository.Save(order);
        emailService.Send(order.Customer.Email, "Order confirmed");
    }
}

Why it's bad: - Dependencies are hidden (not in constructor) - Hard to test (must configure global ServiceLocator) - Runtime errors if service not registered - Couples to ServiceLocator framework

Fix: Constructor injection.

Anti-Pattern: Concrete Dependencies in High-Level Code (DIP Violation)

Example (PHP):

class CheckoutService {
    public function checkout(Cart $cart) {
        $payment = new StripePaymentGateway(); // DIP VIOLATION
        $payment->charge($cart->total());
    }
}

Why it's bad: - Can't switch to PayPal without changing CheckoutService - Can't test without real Stripe API calls - High-level business logic depends on low-level implementation detail

Fix: Depend on IPaymentGateway interface, inject implementation.

Anti-Pattern: Anemic Domain Model (SRP Misapplication)

Description: Separating data from behavior excessively.

Example (TypeScript):

// Anemic model
class Order {
  items: OrderItem[];
  total: number;
  status: string;
  // No behavior
}

// Behavior in separate "service"
class OrderService {
  calculateTotal(order: Order): number {
    return order.items.reduce((sum, item) => sum + item.price, 0);
  }

  validateOrder(order: Order): boolean {
    return order.items.length > 0;
  }
}

Why it's bad: - Order doesn't own its behavior (violates encapsulation) - Business logic scattered across services - OrderService knows too much about Order internals

Fix: Rich domain model -- put behavior on Order class.

Anti-Pattern: Leaky Abstraction (DIP Violation)

Example (Python):

# Abstraction exposes implementation details
class ISQLDatabase(ABC):
    @abstractmethod
    def execute_query(self, sql: str): pass # Leaky: assumes SQL

# Forces non-SQL databases to fake SQL
class MongoDBAdapter(ISQLDatabase):
    def execute_query(self, sql: str):
        # Must translate SQL to MongoDB queries (awkward)
        pass

Why it's bad: - Abstraction assumes SQL (not abstract enough) - Non-SQL implementations are forced into SQL model - Defeats the purpose of abstraction

Fix: Generic abstraction (IDataStore with save(), find(), delete()).