Allyx Gomes

Software Development Blog

The Open/Closed Principle (OCP) — Talking About S.O.L.I.D.

It’s not about putting and taking blocks

I’ve arrived with another post in the S.O.L.I.D. series, and this time I’ll be talking about the Open/Closed Principle (OCP). I’ll never forget the classes I had with my great advisor and mentor Dr. João Carlos Pinheiro, where he often spoke about the benefits of working with code that is open for extension and closed for modification. This is the classic definition of OCP. But what do you mean by “closed for modification,” Allyx? Am I supposed to write a class and never modify it again? Not exactly, it will depend a lot on the context, but let’s get to the main idea of this principle:

The proposal is that every time you need to add new functionality, you shouldn’t have to change existing code, instead, you should always create new code and work with abstractions that allow such flexibility.

Alright, but let’s get to the practical side. I’ll bring you an example that I used in my thesis (page 28 in portuguese). This example deals with a simple block of code where there is a class responsible for managing prints. According to the type of print requested (and passed) by the client, a print message is displayed. Example below:

class PrintGenerator
  def print(type)
    case type
    when "CSV"
      puts "Print CSV"
    when "PDF"
      puts "Print PDF"
    when "DOT_MATRIX"
      puts "Print DOT_MATRIX"
    else
      puts "Unknown print type"
    end
  end
end

The code in the listing above exposes print types within a generator. If at any other point in the system, it becomes necessary to identify these print types for a similar operation, they would need to be handled individually, making maintenance more difficult. A solution is to create an abstraction for the print types that has a contract specifying the desire to print, leaving the implementation details of how to print up to those who implement this abstraction.

class PrintType
  def initialize(value)
    @value = value
  end

  def to_s
    @value
  end

  def print
    raise NotImplementedError, "Subclasses must implement the `print` method"
  end

  class PDF < PrintType
    def initialize
      super("PDF")
    end

    def print
      puts "Print PDF"
    end
  end

  class CSV < PrintType
    def initialize
      super("CSV")
    end

    def print
      puts "Print CSV"
    end
  end

  class DotMatrix < PrintType
    def initialize
      super("DOT_MATRIX")
    end

    def print
      puts "Print DOT_MATRIX"
    end
  end
end

With the modified implementation, it will be easier to add new print types in the future without impacting the existing ones. Although the example is quite educational and doesn’t contain “printing code,” I hope the message about using abstractions to avoid changing “what’s already working” has been clearly understood. See you soon!

Leave a Reply

Your email address will not be published. Required fields are marked *