← Back to quizzesFree quiz

Open Closed Principle Application

The Open/Closed Principle is one of the five SOLID principles that guide robust software design. It states that software entities (classes, modules, functions) should be open for extension…

10 questions~5 min
Open Closed Principle Application — Qwi
0 / 10
Score: 0%
1

Which design change best illustrates the Open Closed Principle for a payment processing system?

2

In the notification system example, what is the primary benefit of using a NotificationChannel interface?

3

Which of the following is a common violation of OCP according to the text?

4

If a new payment gateway must be added, which step complies with OCP?

5

What problem arises from a 'God class' in the context of OCP?

6

Which design pattern most directly supports the Open Closed Principle as described in the scenarios?

7

A developer replaces a NotificationService's switch-case with a series of if‑else statements for each channel. Which OCP violation does this most likely represent?

8

When extending a system with a new payment method, why is it preferable to add a new class rather than edit an existing one?

9

Which of the following statements best describes a 'monolithic interface' violation of OCP?

10

In the payment processing example, which principle works together with OCP to achieve low coupling?

Understanding the Open/Closed Principle (OCP)

The Open/Closed Principle is one of the five SOLID principles that guide robust software design. It states that software entities (classes, modules, functions) should be open for extension but closed for modification. In practice, this means you can add new behavior without changing existing, tested code. Doing so reduces regression bugs, eases testing, and promotes a more maintainable codebase.

Why OCP Matters

  • Stability: Existing functionality remains untouched, preserving its proven behavior.
  • Scalability: New features can be introduced by adding new modules rather than rewriting old ones.
  • Testability: Isolated extensions are easier to unit‑test because they don’t interfere with legacy code.
  • Collaboration: Teams can work on new extensions in parallel without stepping on each other's toes.

Common OCP Violations

Recognizing anti‑patterns helps you avoid them. Typical violations include:

  • Using extensive conditional statements (switch, if‑else) to handle new scenarios.
  • Modifying a “God class” that centralizes many responsibilities, forcing you to edit the same file for every new feature.
  • Hard‑coding dependencies, which creates tight coupling between modules.

Applying OCP in Real‑World Scenarios

Let’s explore how OCP is applied in two common domains: payment processing and notification services.

1. Payment Processing System

Imagine a system that must support multiple payment gateways (credit cards, PayPal, crypto, etc.). A naïve implementation might look like this:

class PaymentProcessor {
    public function process($type, $data) {
        switch ($type) {
            case 'credit': // logic
            case 'paypal': // logic
            // …
        }
    }
}

This design violates OCP because every time a new gateway is added, you must edit the switch block. The correct approach is to depend on an abstraction—a Payment interface—and let each gateway implement it.

  • Step 1: Define a Payment interface with a pay() method.
  • Step 2: Create concrete classes such as CreditCardPayment, PayPalPayment, each implementing Payment.
  • Step 3: Refactor PaymentProcessor to depend on the Payment abstraction, typically via constructor injection.

When a new gateway is required, you simply add a new class that implements Payment and register it with the dependency injection container—no changes to existing code.

2. Notification System

Consider a NotificationService that sends alerts via email, SMS, or push notifications. An OCP‑compliant design introduces a NotificationChannel interface:

interface NotificationChannel {
    public function send(string $message): void;
}

class EmailChannel implements NotificationChannel { /* ... */ }
class SmsChannel implements NotificationChannel { /* ... */ }
class PushChannel implements NotificationChannel { /* ... */ }

class NotificationService {
    private array $channels;
    public function __construct(array $channels) { $this->channels = $channels; }
    public function notify(string $msg) {
        foreach ($this->channels as $channel) {
            $channel->send($msg);
        }
    }
}

The primary benefit of this interface is that new channels can be added without touching the service code. The service remains closed for modification while being open for extension through new implementations.

Design Patterns that Support OCP

Several well‑known patterns embody OCP principles. The most directly relevant to the examples above is the Strategy pattern. It encapsulates interchangeable algorithms (payment methods, notification channels) behind a common interface, allowing the client to select the appropriate strategy at runtime.

  • Strategy Pattern: Enables swapping of concrete implementations without altering the context class.
  • Factory Pattern: Often used alongside Strategy to instantiate the correct concrete class based on configuration.
  • Observer Pattern: Useful for broadcasting events, but it does not directly address the need to keep a class closed for modification.

Why Strategy Beats Switch‑Case

Replacing a switch with a series of if‑else statements, as seen in a flawed refactor of a notification service, merely moves the violation from one form to another. Both approaches tightly couple the service to concrete implementations, making future extensions risky and error‑prone. The Strategy pattern eliminates this coupling by delegating responsibility to separate, interchangeable classes.

Practical Steps to Make Your Code OCP‑Compliant

  1. Identify Variation Points: Determine where behavior is likely to change (e.g., payment methods, notification channels).
  2. Extract Interfaces: Create abstractions that capture the required behavior.
  3. Implement Concrete Strategies: Write separate classes for each variation, adhering to the interface.
  4. Use Dependency Injection: Inject the appropriate implementation at runtime, keeping the consumer class closed for modification.
  5. Register New Implementations: Add new classes to your DI container or factory without touching existing code.

Case Study: Adding a New Payment Gateway

Suppose you need to integrate CryptoPay. Follow these steps:

  • Create CryptoPayGateway implements Payment with its own pay() logic.
  • Register CryptoPayGateway in the service container.
  • Configure the system (e.g., via a config file) to use CryptoPayGateway when the user selects crypto.

Notice that PaymentProcessor remains untouched—demonstrating true adherence to OCP.

Testing and Maintaining OCP‑Friendly Code

Because each concrete implementation is isolated, unit tests can target them individually. Mocking the Payment or NotificationChannel interfaces allows you to verify that higher‑level services interact correctly without invoking real external systems.

  • Write interface tests: Ensure contracts are fulfilled.
  • Test each strategy class: Validate its specific behavior.
  • Integration tests: Confirm that the DI container resolves the correct implementations.

Avoiding the “God Class” Pitfall

A “God class” aggregates many responsibilities, making it a hotspot for OCP violations. When you need to extend functionality, you’re forced to edit this massive class, risking side effects. Refactor such classes by extracting interfaces and applying the Strategy pattern, thereby distributing responsibilities across focused, cohesive classes.

Key Takeaways

  • OCP encourages extension without modification, protecting existing behavior.
  • Use interfaces and the Strategy pattern to encapsulate varying behavior.
  • Avoid extensive conditional logic and “God classes” that centralize change.
  • Leverage dependency injection to keep high‑level modules closed for modification.
  • Testing becomes simpler when each variation is isolated behind an abstraction.

By applying these principles, developers can build systems that grow gracefully, remain robust under change, and are easier to understand and maintain. Embrace OCP today, and watch your codebase become more resilient and adaptable.