Home For OCP, What Exactly Are "Extensions" and "Modifications"?
Post
Cancel

For OCP, What Exactly Are "Extensions" and "Modifications"?

The Open/Closed Principle (OCP) is a cornerstone in crafting maintainable, scalable, and robust OOP systems. It encourages us to think ahead, design with flexibility in mind, and respect the existing functionality that our software provides. By embracing OCP, we align our work with the ever-evolving nature of software, ensuring that our systems grow gracefully in the face of change.

In essence, the Open/Closed Principle encourages us to write code that’s resilient to change yet flexible enough to evolve. Understanding the distinction between extending and modifying code is crucial for applying OCP correctly. As developers, we should aim not just to adhere to such principles but to understand their underlying philosophy, ensuring our software architectures are robust, adaptable, and maintainable.


Contents


Introduction to OCP

What is the Open/Closed Principle?

Formulated by Bertrand Meyer, the Open/Closed Principle states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means that the behavior of a module can be extended without altering its source code.

Benefits of Embracing OCP

  • Ease of Maintenance: Changes in requirements or additions of new features have minimal impact on existing code.
  • Enhanced Scalability: The system becomes more scalable with the ability to introduce new behaviors without changing existing code.
  • Reduced Testing Overhead: Minimizing changes in existing code reduces the need for extensive regression testing.

Understanding “Extensions” and “Modifications”

What Does “Extension” Mean?

  • In OOP, an “extension” involves adding new functionality or behaviour to a system without altering its existing code. It’s akin to building an extension to a house without modifying the original structure.
    • Example: Implementing a new Shape class like Triangle that implements a Shape interface without altering the interface or any of the existing implementations like Circle or Rectangle.

What Does “Modification” Mean?

  • A “modification” refers to changing the existing codebase, typically to alter its behavior or functionality. This may include minor adjustments to a method, or it may include an overhaul of a class’s internal logic.
    • Example: Changing the logic inside the area method of a Rectangle class.

Open to extensions, closed to modifications encapsulates the idea that you should be able to add new features or components to a system without changing the existing code. This principle fosters a design that accommodates growth and change while preserving the stability and reliability of the existing system.

Does Modifying Existing Code Necessarily Violate OCP?

Does modifying existing code always violate OCP? Of course not.

  • In practical development, some code changes are inevitable.
    • We need to realise that it is impossible to add a new feature without “modifying” the code of any module, class or method.
    • What we need to do is to try to make the modifications more focused, and try to make the core part of the logic code to meet OCP.
  • The open/close principle can be applied to code at different levels of granularity, be it a module, a class, or a method (and its attributes).
    • The same code change may be recognised as a “modification” at a coarse code granularity, and as an “extension” at a finer code granularity.
    • For example, adding attributes and methods at the class level, this code change can be recognised as a “modification”; however, this code change does not modify existing attributes and methods, and at the method (and its attribute) level, it can be recognised as an “extension”.

In fact, we don’t need to worry about whether a code change is a “modification” or an “extension”, and we also don’t need to worry too much about whether it violates the “open/close principle”. OCP does not mean that modifications are completely eliminated, but rather that the development of new features is accomplished with a minimum of modification to the code.

In order to write scalable code as much as possible, we need to keep a conscious of extension, abstraction, and encapsulation. This “subconsciousness” is probably more important than any development skills and principle.

How to Apply OCP Flexibly in Real Development?

  • It depends on the specific projects:
    • If you are developing a business-oriented system (e.g., e-commerce system, logistics system, etc.) to identify as many extension points as possible, you need to understand the business well enough to know the current and future business requirements that you may want to support.
    • If you are developing non-business related, generic, low-level systems (e.g., frameworks, components, class libraries), you need to understand “how will they be used? What features are you going to add in the future? What additional functionality will users need in the future?” etc.

However, there is a saying that the only thing that doesn’t change is change itself. Even if we know the business and the system well enough, then it’s impossible to identify all the extension points. Even if you could identify all the expansion points and reserve them for all those places, the cost of doing so would be unacceptable.

We don’t need to over-design for requirements that are distant and don’t necessarily happen.

  • The most reasonable approach is to design extensions in advance for some appropriate cases. These cases include:
    • extensions that are relatively certain;
    • extensions that are likely to appear in the short term;
    • extensions where changes in requirements have a large impact on the code structure;
    • extensions that are not costly to implement.
  • However, for cases where extensions are not appropriate, we can wait until there is a demand-driven time to implement extensions by refactoring the code. These cases include:
    • some requirements that we are not sure we want to support in the future;
    • extensions that are very complex to implement.

Some Practices of OCP

Use of Abstraction and Polymorphism

  • Employ interfaces or abstract classes to create a stable foundation that clients can rely on.
  • Implement functionality through classes that extend these abstractions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface Shape {
    double area();
}

public class Rectangle implements Shape {
    private double width, height;

    // Constructor, getters, and setters

    @Override
    public double area() {
        return width * height;
    }
}

Strategy Pattern for Dynamic Behavior

  • Leverage the Strategy Pattern to dynamically change the behavior of a class.
  • Define a family of algorithms, encapsulate each one, and make them interchangeable.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface SortingStrategy {
    void sort(List<?> items);
}

public class QuickSort implements SortingStrategy {
    @Override
    public void sort(List<?> items) {
        // Implement quicksort algorithm
    }
}

public class MergeSort implements SortingStrategy {
    @Override
    public void sort(List<?> items) {
        // Implement quicksort algorithm
    }
}

Dependency Inversion for Greater Flexibility

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • This inversion of dependency enhances the ability to extend module behavior.
1
2
3
4
5
6
7
8
9
10
11
12
public class DataProcessor {
    private SortingStrategy sorter;

    public DataProcessor(SortingStrategy sorter) {
        this.sorter = sorter;
    }

    public void process(List<?> items) {
        sorter.sort(items);
        // Further processing
    }
}
This post is licensed under CC BY 4.0 by the author.
ip