OCP — Principles, Openness for Extension, and Closedness for Modification

Author: IT Sectr Published: 2026-05-11 Reading time: 9 min

OCP (Open/Closed Principle) is the second principle of SOLID, which states: software entities should be open for extension but closed for modification. This principle, formulated by Bertrand Meyer in 1988, allows adding new functionality without changing existing code. According to Robert Martin's book Clean Architecture (2017), the openness principle is implemented through abstractions and polymorphism, minimizing the risk of regression errors.

Key Takeaways

  • OCP — the principle of openness for extension and closedness for modification
  • Extension is implemented through abstractions, interfaces, and polymorphism
  • Modification of existing code is prohibited — new functionality is added without altering old classes
  • Polymorphism — the key mechanism of OCP in object-oriented languages
  • Violating OCP leads to cascading changes when adding new requirements

What Is OCP (Open/Closed Principle)?

OCP (Open/Closed Principle) — the principle of openness for extension and closedness for modification. Classes, modules, and functions should be designed so that new behavior can be added without changing their source code. Extension is achieved through inheritance, composition, or substitution of interface implementations.

Bertrand Meyer in his book Object-Oriented Software Construction (1988) first described OCP through inheritance: the base class remains unchanged, while subclasses extend its behavior. The modern interpretation of OCP, proposed by Robert Martin, relies on polymorphism and interfaces: instead of inheritance, abstract contracts are used.

The difference between approaches is significant. Inheritance creates a tight coupling between base and derived classes. Interfaces and composition provide flexibility: the implementation can be swapped without changing client code. Modern OCP is about abstraction, not inheritance.

Polymorphism as the Foundation of OCP

Polymorphic OCP uses abstract classes or interfaces to define a contract. Client code works with the abstraction without knowing the concrete implementation. New functionality is added by creating a new class that implements the same interface — without a single change to existing code. This makes the system resistant to change and predictable for extension.

In mobile development, this approach is ubiquitous: the Strategy pattern allows swapping algorithms (image compression, caching, authentication) through a single interface. Adding a new strategy does not require changing the code that uses it.

How to Implement the Open/Closed Principle

Implementing OCP begins with isolating changeable behavior into an abstraction. If there is a switch construct or an if-else chain checking the type of an object in the code — this is a signal to apply OCP. Each conditional branch potentially requires adding a new branch when extending.

The refactoring process under OCP includes three steps: identify the changeable aspect (what can be extended), isolate it into an interface or abstract class, rewrite client code to work with the abstraction instead of the concrete class. After this, new functionality is added without changing the client.

An important clarification: closedness for modification is not absolute. If a requirement change affects the abstraction itself or the contract — change is inevitable. OCP protects against changes in implementations, not in contracts. Good design assumes contracts are stable and implementations are variable.

When evaluating OCP compatibility of an architecture, it is useful to look at extension points. Each point where a developer adds if-else or switch for a new type is a candidate for abstraction. A system designed according to OCP has predictable extension points: interfaces with documentation saying "implement this interface to add a new type." In Android, a prime example is the Factory pattern paired with ViewModelProvider.Factory — adding a new ViewModel type does not require changing existing factories.

Strategies and Patterns for OCP

The most effective patterns for adhering to OCP in mobile development include Strategy, Template Method, Decorator, and Factory. Each of them solves the problem of extending behavior without modifying existing code through different object-oriented design mechanisms.

Strategy allows swapping algorithms on the fly through a common interface. In iOS development, strategies are used for animations and form validation. Template Method defines the skeleton of an algorithm in a base class, and subclasses override the steps — suitable for screens with a common structure but different content.

Decorator dynamically adds behavior to an object without changing its class. In Android, Decorator is used for wrapping a Repository with a caching or logging layer. Factory Method creates objects through an interface, allowing subclasses to decide which class to instantiate — the foundation of OCP-compatible dependency creation.

Choosing a Strategy for a Mobile Project

Pattern selection depends on the stability of the behavior being extended. Strategy is optimal when algorithms are replaced entirely. Template Method — when the structure is fixed but steps vary. Decorator — when the extension should be transparent to the client. For most scenarios in Android and iOS, Strategy + dependency injection is sufficient.

Applying these patterns without OCP is technically possible but loses its purpose. It is OCP that justifies why we introduce an additional level of abstraction: so that the system can grow without rewriting existing code.

OCP Examples in Mobile Applications

Let's consider an Android example with payment processing. Without OCP, each new payment system requires changes to the handler class. With OCP, a new interface implementation is added without modifying existing code.

kotlin
// OCP violation: switch requires modification when adding a new system
class BadPaymentProcessor {
    fun process(type: String) {
        when (type) {
            "card" -> // card processing
            "paypal" -> // PayPal processing
        }
    }
}

// OCP-compatible design
interface PaymentMethod {
    fun pay(amount: Double)
}

class CardPayment : PaymentMethod {
    override fun pay(amount: Double) { }
}

class PayPalPayment : PaymentMethod {
    override fun pay(amount: Double) { }
}

// New system — new class, without changing existing code
class ApplePayPayment : PaymentMethod {
    override fun pay(amount: Double) { }
}

An iOS example with text field validation demonstrates the same logic through Swift protocols:

swift
// OCP-compatible validation
protocol ValidationRule {
    func validate(_ input: String) -> Bool
}

struct EmailRule: ValidationRule {
    func validate(_ input: String) -> Bool {
        return input.contains("@")
    }
}

struct PhoneRule: ValidationRule {
    func validate(_ input: String) -> Bool {
        return input.count == 11
    }
}

// Adding a new rule does not require changing the validator code
struct PasswordRule: ValidationRule {
    func validate(_ input: String) -> Bool {
        return input.count >= 8
    }
}

The key advantage of OCP in these examples: adding ApplePay or PasswordRule does not require modifying existing classes. The code expands horizontally — through new files, not by altering old ones. This reduces the risk of regression and accelerates the implementation of new functionality.

Common Mistakes When Violating OCP

The most common violation is a switch or when construct based on object type. Each time a new type is added, you have to find all such switch statements in the code and add a new branch. A missed switch is a runtime bug that is difficult to detect at compile time.

In mobile development, OCP is violated when using giant enum classes with methods that depend on the enum value. Adding a new enum element requires changing every switch across the entire project. The alternative is polymorphism through an interface, where each type implements its own behavior.

Another typical violation is the God Adapter: RecyclerView.Adapter (Android) or UITableViewDataSource (iOS) that handles different cell types through if-else. Each new cell type requires extending the adapter. The solution is a polymorphic ViewHolder with a common bind method, where each cell type is responsible for its own rendering.

How to Avoid Violating OCP

Preventive measures include: avoiding type-based switch in favor of polymorphism, injecting dependencies through interfaces, and using the Factory pattern for creating objects based on configuration. Analyzing code for "type switches" is a mandatory part of code review in OCP-oriented teams.

Refactoring an existing OCP violation is done through Replace Conditional with Polymorphism: each conditional branch becomes a separate class implementing a common interface. Client code is rewritten to work with the interface, and the concrete implementation is supplied through a factory or DI container.

It is important to understand that OCP and polymorphism do not solve all extension problems. If the architecture is chosen incorrectly, adding new functionality will require changing not only implementations but also contracts. Good architecture predicts extension directions and places abstractions exactly at those points. Investments in OCP pay off more the longer the project lives and the more often requirements change for specific modules.

Frequently Asked Questions

Does OCP mean that code cannot be changed at all?

No. OCP prohibits changing existing code when adding new functionality related to the same abstraction. Changing a contract, fixing bugs, and refactoring are not OCP violations — the principle protects against cascading changes during extension.

How is OCP related to the Strategy pattern?

Strategy is a direct implementation of OCP. The strategy interface defines the contract, the client depends on the abstraction, and concrete strategies implement variable behavior. Adding a new strategy does not require changing the client — this is openness for extension with closedness for modification.

Can OCP be followed without interfaces?

Yes, through inheritance and Template Method: the base class defines the algorithm skeleton, and subclasses override the steps. However, inheritance creates tight coupling and is less flexible than interfaces. In modern development, interfaces and composition are considered the preferred way to implement OCP.

How does OCP affect testing?

OCP-compatible code simplifies testing: each interface implementation is tested in isolation. Client code is tested with a mock implementation, allowing logic to be verified without binding to specific behavior. Expanding the system does not require rewriting existing tests.

Should one always strive for OCP?

No. OCP is justified when functional extension is predictable. For stable code that is not planned to be extended, additional abstraction is excessive. YAGNI (You Ain't Gonna Need It) is a good counterbalance to OCP: abstraction is introduced when a second behavior variant appears, not preemptively.

Summary

  • OCP (Open/Closed Principle) — the principle of openness for extension and closedness for modification
  • Extension is implemented through interfaces, polymorphism, and composition instead of inheritance
  • Type-based switch — the main antipattern that violates OCP and requires changes with each new type
  • Strategy and Template Method — the main patterns for adhering to OCP in mobile projects
  • Polymorphism replaces conditional constructs and makes code extensible without modification
  • Refactoring an OCP violation is done through Replace Conditional with Polymorphism
  • YAGNI limits OCP: abstraction is introduced when a second implementation appears, not in advance

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also