0

I'm trying to reach a better understanding of the Open/Closed principle. I'm familiar with reference material such as

Robert Martin's explanation

and Jon Skeet's exploration of the ideas, and the related concept of Protected Variation.

I have a nagging feeling that I still haven't got to essence of the Open/Closed Principle. One approach I have to increasing understanding of a concept is to explore the negation or inversion of the idea. I'm having trouble coming up with a concrete example of a violation of the Open/Closed principle - my hope is that if we have such an example we can point to it and say "Look at the unfortunate results of designing that way, how much better things would be if we were Open/Closed."

So, the question. Can you give a non-trivial example of, say, a Java class that is Closed for Extension or Open for Modification and why that would be a bad thing.

Obviously there are trivial cases such as making a class final so inheritance is barred but I don't think that's the core of the Open/Closed principle.

StepUp
  • 36,391
  • 15
  • 88
  • 148
djna
  • 54,992
  • 14
  • 74
  • 117

3 Answers3

2

Generally, whenever you see an enumeration of possibilities, you're very likely seeing a violation of the Open/Closed principle.

interface Drawing: {
   addCircle(...);
   addSquare(...);
   // etc..
}

obviously this interface, and all implementations, will have to be modified whenever someone wants to support a new type of shape.

Following the open/closed principle, you might do something like:

interface Drawing {
  addShape(shape: IShape)
}

Here, the author has defined an interface to specify what is required of a shape, and allowed the caller to add any shape at all, as long as it implements the interface, and no modifications are required.

Matt Timmermans
  • 53,709
  • 3
  • 46
  • 87
1

Another example could be a simple calculator:

public enum Operation
{
    Plus, Minus, Multiply, Divide
}

If we want to add a new operation, then we should add the new 'if else' statement. It is violation of Open closed principle

public class Calculator
{
    public decimal Execute(int a, int b, Operation operation)
    {
        if (operation == Operation.Minus)
            return a - b;
        else if (operation == Operation.Plus)
            return a + b;
        // so if we want to an new operation, then we should add new
        // 'if else' statements. It is violation of Open closed principle

        return a * b;
    }
}

It can be improved by creating interface:

public interface ICalculatorOperation 
{
    decimal Execute(int a, int b);
}

And, in fact, we reversed a dependency from a lower level of component to a higher level of component. So "lower level" components are dependent upon the interfaces owned by the "higher level" components.

public class Calculator
{
    public decimal Execute(ICalculatorOperation calculatorOperation, int a, int b)
    {
        return calculatorOperation.Execute(a, b);
    }
}

and a factory class that provides dependencies:

public interface ICalculatorOperation 
{
    decimal Execute(int a, int b);
}

public class SumOperation : ICalculatorOperation
{
    public decimal Execute(int a, int b) =>  a + b;
}

public class CalculatorOperationFactory
{
    private Dictionary<Operation, ICalculatorOperation> _operationByType = 
        new Dictionary<Operation, ICalculatorOperation>()
    {
        {Operation.Plus, new SumOperation() }
        // ... other code is omitted for the brevity
    };

    public ICalculatorOperation GetByOperation(Operation operation) => 
        _operationByType[operation];
}

And then based on what a button was clicked, you can choose an appropriate operation (we use strategy pattern in the above code) to handle this button using the following code like this:

CalculatorOperationFactory calculatorOperationFactory = new ();
ICalculatorOperation calculatorOperation = 
    calculatorOperationFactory.GetByOperation(Operation.Plus); // here 
    // we are assuming that user clicked "plus" button 
decimal sum = new Calculator().Execute(calculatorOperation, 1, 2);

And I believe that the above code complies with:

  • Single responsibility principle of SOLID. I mean, that if we want to add new type of operation of calculation, then we should edit just appropriate classes and enums such as enum Operation, CalculatorOperationFactory and CalculatorOperationFactory to extend operations
  • it is easier to test the above code
  • open closed principle. We open the method Execute() of the class Calculator for extension, but closed for modification. What I mean is: a. if we want to edit, e.g. plus operation, then we will edit SumOperation class, not Calculator class. Moreover, By doing this, we also satisfy Single responsibility principle of SOLID b. if we want to add new functionality, e.g. multiple operation, then we will add MultipluOperation class, but it is not necessary to edit Calculator class. Moreover, By doing this, we also satisfy Single responsibility principle of SOLID
StepUp
  • 36,391
  • 15
  • 88
  • 148
1

Here is the sample code in Java for "Closed for Extension or Open for Modification "

This is bad because every time you add new components, say from ServiceD to ServiceZ you have to make modification over the client of the component (that is Program01).

//this violates OCP
public class Program01 {

    private static ServiceA objA;
    private static ServiceB objB;
    private static ServiceC objC;

    public static void set(ServiceA obj) {
        objA = obj;     
    }

    public static void set(ServiceB obj) {
        objB = obj;     
    }

    public static void set(ServiceC obj) {
        objC = obj;     
    }

    public static void doSomething(final String id) {
        if(id.equals("A")){
            objA.doSomething();
        }else if(id.equals("B")){
            objB.doSomething();
        }else if(id.equals("C")){
            objC.doSomething();
        }
    }
}

Whereas if you follow OCP, even if you have new services D-Z, the client (Program02) does not require any modification.

//this follows OCP
public class Program02 {
    static List<Service> listService = new ArrayList<Service>();

    public static void set(Service obj) {
        listService.add(obj);       
    }

    public static void doSomething(final String id) {
        for(Service obj : listService) {
            if(!id.equals(obj.identify()))continue;
            obj.doSomething();  
            break;
        }
    }
}

The OCP definition: "Closed for Modification and Open for Extension," can be rephrased as, "New components can be added and extended(OPEN) without modifying the client of components(CLOSED)."

This principle especially shines when a client has numerous components to manage, and the number of components continues to grow.

Tando
  • 715
  • 1
  • 7
  • 16