Files
gruperly/.agents/skills/clean-code-principles/rules/solid-ocp-extension.md
2026-09-04 16:49:24 -03:00

6.1 KiB

id, title, category, priority, tags, related
id title category priority tags related
solid-ocp-extension SOLID - Open/Closed Principle (Extension) solid-principles critical
SOLID
OCP
open-closed
extensibility
design-patterns
solid-ocp-abstraction
pattern-repository
solid-dip-abstractions

Open/Closed Principle - Extension

Software entities should be open for extension but closed for modification. Add new functionality by adding new code, not by changing existing code.

Bad Example

// Anti-pattern: Modifying existing code to add new functionality
class PaymentProcessor {
  processPayment(payment: Payment): PaymentResult {
    switch (payment.method) {
      case 'credit_card':
        return this.processCreditCard(payment);
      case 'paypal':
        return this.processPayPal(payment);
      case 'stripe':
        return this.processStripe(payment);
      // Every new payment method requires modifying this class
      case 'apple_pay':
        return this.processApplePay(payment);
      case 'google_pay':
        return this.processGooglePay(payment);
      case 'crypto':
        return this.processCrypto(payment);
      default:
        throw new Error(`Unknown payment method: ${payment.method}`);
    }
  }

  private processCreditCard(payment: Payment): PaymentResult {
    // Credit card logic
  }

  private processPayPal(payment: Payment): PaymentResult {
    // PayPal logic
  }

  private processStripe(payment: Payment): PaymentResult {
    // Stripe logic
  }

  private processApplePay(payment: Payment): PaymentResult {
    // Apple Pay logic
  }

  private processGooglePay(payment: Payment): PaymentResult {
    // Google Pay logic
  }

  private processCrypto(payment: Payment): PaymentResult {
    // Crypto logic
  }
}

// Adding a new payment method requires:
// 1. Adding a new case to the switch statement
// 2. Adding a new private method
// 3. Testing the entire class again

Good Example

// Correct approach: Open for extension, closed for modification

// Define the contract for payment handlers
interface PaymentHandler {
  readonly methodType: string;
  canHandle(payment: Payment): boolean;
  process(payment: Payment): Promise<PaymentResult>;
  validate(payment: Payment): ValidationResult;
}

// Payment processor that never needs modification
class PaymentProcessor {
  private handlers: Map<string, PaymentHandler> = new Map();

  registerHandler(handler: PaymentHandler): void {
    this.handlers.set(handler.methodType, handler);
  }

  async processPayment(payment: Payment): Promise<PaymentResult> {
    const handler = this.handlers.get(payment.method);

    if (!handler) {
      throw new UnsupportedPaymentMethodError(payment.method);
    }

    const validation = handler.validate(payment);
    if (!validation.isValid) {
      throw new PaymentValidationError(validation.errors);
    }

    return handler.process(payment);
  }

  getSupportedMethods(): string[] {
    return Array.from(this.handlers.keys());
  }
}

// Each payment method is a separate class that can be added without modifying PaymentProcessor

class CreditCardPaymentHandler implements PaymentHandler {
  readonly methodType = 'credit_card';

  canHandle(payment: Payment): boolean {
    return payment.method === this.methodType;
  }

  validate(payment: Payment): ValidationResult {
    const errors: string[] = [];
    if (!payment.cardNumber || payment.cardNumber.length !== 16) {
      errors.push('Invalid card number');
    }
    if (!payment.cvv || payment.cvv.length !== 3) {
      errors.push('Invalid CVV');
    }
    return { isValid: errors.length === 0, errors };
  }

  async process(payment: Payment): Promise<PaymentResult> {
    // Credit card processing logic
    return { success: true, transactionId: generateId() };
  }
}

class PayPalPaymentHandler implements PaymentHandler {
  readonly methodType = 'paypal';

  canHandle(payment: Payment): boolean {
    return payment.method === this.methodType;
  }

  validate(payment: Payment): ValidationResult {
    const errors: string[] = [];
    if (!payment.paypalEmail) {
      errors.push('PayPal email required');
    }
    return { isValid: errors.length === 0, errors };
  }

  async process(payment: Payment): Promise<PaymentResult> {
    // PayPal processing logic
    return { success: true, transactionId: generateId() };
  }
}

// Adding a new payment method - no modification to existing code!
class CryptoPaymentHandler implements PaymentHandler {
  readonly methodType = 'crypto';

  canHandle(payment: Payment): boolean {
    return payment.method === this.methodType;
  }

  validate(payment: Payment): ValidationResult {
    const errors: string[] = [];
    if (!payment.walletAddress) {
      errors.push('Wallet address required');
    }
    if (!payment.cryptoCurrency) {
      errors.push('Cryptocurrency type required');
    }
    return { isValid: errors.length === 0, errors };
  }

  async process(payment: Payment): Promise<PaymentResult> {
    // Crypto processing logic
    return { success: true, transactionId: generateId() };
  }
}

// Application setup - register handlers
const processor = new PaymentProcessor();
processor.registerHandler(new CreditCardPaymentHandler());
processor.registerHandler(new PayPalPaymentHandler());
processor.registerHandler(new CryptoPaymentHandler());
// Add new payment methods by simply registering new handlers

Why

  1. Stability: The PaymentProcessor class is stable and tested. Adding new payment methods doesn't risk breaking existing functionality.

  2. Team Scalability: Different developers can work on different payment handlers simultaneously without merge conflicts.

  3. Plugin Architecture: Payment handlers can be loaded dynamically, even from external packages or plugins.

  4. Testing Isolation: Each handler is tested independently. New handlers don't require retesting old ones.

  5. Feature Flags: New payment methods can be enabled/disabled by simply registering or not registering the handler.

  6. Compliance: When regulations change for one payment method, only that handler needs updating.

  7. Rollback Safety: If a new payment handler has issues, remove it without touching proven code.