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