26 Oct 2023



Intermediate

Here are examples of Open-Closed Principle (OCP) in real-world scenarios, along with solutions that adhere to OCP principles. Each example includes a problem statement, a code snippet demonstrating the problem, and a proposed solution:

Example 1: Problem in E-commerce Discounts

Problem: You have a discount calculation system for an e-commerce application, but it's not open for extension. Adding a new discount type requires modifying the existing code.

Problematic Code:

class DiscountCalculator {
    double calculateDiscount(Order order, String discountType) {
        if (discountType.equals("Percentage")) {
            // Calculate percentage discount
        } else if (discountType.equals("FixedAmount")) {
            // Calculate fixed amount discount
        } else if (discountType.equals("NewDiscountType")) {
            // Calculate new discount type (requires code modification)
        }
    }
}

Solution: The solution involves introducing an abstract Discount class and creating concrete subclasses like PercentageDiscount and FixedAmountDiscount that extend this abstract class. By doing this, new discount types can be added by creating additional subclasses without modifying the existing discount calculation code.

abstract class Discount {
    abstract double calculateDiscount(Order order);
}

class PercentageDiscount extends Discount {
    double calculateDiscount(Order order) {
        // Calculate percentage discount
    }
}

class FixedAmountDiscount extends Discount {
    double calculateDiscount(Order order) {
        // Calculate fixed amount discount
    }
}

// NewDiscountType can be added without modifying existing code by creating a new class.

Example 2: Problem in Logging and Auditing

Problem: You have a logging system in your application, but it's tightly coupled to a specific logging implementation (e.g., file logging). It's not open for extension to add other log destinations.

Problematic Code:

class Logger {
    void logToFile(String message) {
        // Log message to a file
    }
}

Solution: The solution is to define a common Logger interface with a log method. Concrete classes like FileLogger and DatabaseLogger implement this interface. This allows adding new log destinations by creating new classes that implement the Logger interface without modifying the core logging code.

interface Logger {
    void log(String message);
}

class FileLogger implements Logger {
    void log(String message) {
        // Log message to a file
    }
}

class DatabaseLogger implements Logger {
    void log(String message) {
        // Log message to a database
    }
}

// New log destinations can be added by creating new classes that implement the Logger interface.

Example 3: Problem in Payment Gateways

Problem: Your payment processing system only supports a single payment gateway. Adding support for new payment gateways requires modifying existing code.

Problematic Code:

class PaymentProcessor {
    boolean processPayment(Order order) {
        // Process payment using a specific payment gateway
    }
}

Solution: The solution is to create a PaymentGateway interface and implement concrete classes like CreditCardGateway and PayPalGateway that adhere to this interface. This design allows new payment gateways to be added without changing the core payment processing code.

interface PaymentGateway {
    boolean processPayment(Order order);
}

class CreditCardGateway implements PaymentGateway {
    boolean processPayment(Order order) {
        // Process payment using a credit card gateway
    }
}

class PayPalGateway implements PaymentGateway {
    boolean processPayment(Order order) {
        // Process payment using PayPal
    }
}

// New payment gateways can be added by creating new classes that implement the PaymentGateway interface.

Example 4: Problem in Content Filters

Problem: You have content filtering functionality that's hard-coded to work with only one type of content (e.g., HTML). Adding support for new content types requires modifying existing code.

Problematic Code:

class ContentFilter {
    String filterHTML(String content) {
        // Apply HTML filtering to content
    }
}

Solution: The solution involves introducing a ContentFilter interface with a filterContent method. Concrete classes like HTMLFilter and TextFilter implement this interface, allowing the addition of new content filters by creating new classes that adhere to the interface.

interface ContentFilter {
    String filterContent(String content);
}

class HTMLFilter implements ContentFilter {
    String filterContent(String content) {
        // Apply HTML filtering to content
    }
}

class TextFilter implements ContentFilter {
    String filterContent(String content) {
        // Apply text filtering to content
    }
}

// New content filters can be added by creating new classes that implement the ContentFilter interface.

Example 5: Problem in UI Components

Problem: Your UI framework is limited to a fixed set of UI components. Extending the framework with new components requires modifying the existing code.

Problematic Code:

class UIFramework {
    void createButton() {
        // Create a button component
    }
}

Solution: The solution is to create a base UIComponent class with common properties and methods. Custom components like UIButton and UITextBox extend this base class, enabling the addition of new UI components without altering the core framework.

class UIComponent {
    // Common UI component properties and methods
}

class UIButton extends UIComponent {
    // Custom button-specific properties and methods
}

class UITextBox extends UIComponent {
    // Custom text box-specific properties and methods
}

// New UI components can be added by creating new classes that extend UIComponent.

Example 6: Problem in Database Access Layers

Problem: Your database access layer is tightly coupled to a specific database system. Adding support for a new database system requires modifying existing code.

Problematic Code:

class DatabaseAccess {
    ResultSet executeMySQLQuery(String query) {
        // Execute a MySQL query
    }
}

Solution: The solution is to define a DatabaseConnection interface with an executeQuery method. Concrete classes like MySQLConnection and PostgreSQLConnection implement this interface, allowing new database connections to be added without altering the core database access layer.

interface DatabaseConnection {
    ResultSet executeQuery(String query);
}

class MySQLConnection implements DatabaseConnection {
    ResultSet executeQuery(String query) {
        // Execute MySQL query
    }
}

class PostgreSQLConnection implements DatabaseConnection {
    ResultSet executeQuery(String query) {
        // Execute PostgreSQL query
    }
}

// New database connections can be added by creating new classes that implement the DatabaseConnection interface.

Example 7: Problem in Plugin Systems

Problem: Your application doesn't support plugins or extensions. Adding new functionality requires modifying the core application code.

Problematic Code:

class Application {
    void doSomething() {
        // Core functionality
    }
}

Solution: The solution is to modify the application to include a list of plugins. Plugins can be added to the application, and they are executed alongside the core functionality. This design allows new features or functionalities to be added via plugins without changing the core application code.

class Application {
    List<Plugin> plugins;

    void addPlugin(Plugin plugin) {
        plugins.add(plugin);
    }

    void doSomething() {
        // Core functionality
        ...

        // Execute plugins
        for (Plugin plugin : plugins) {
            plugin.execute();
        }
    }
}

// New functionality can be added via plugins without modifying the core application code.

In each of these examples, the solution adheres to the Open-Closed Principle by allowing for the extension of functionality without modifying existing code. This approach promotes maintainability, scalability, and reduced risk of introducing bugs when adding new features or functionalities.

solid-principles
open-closed-principle
examples