Created by Stan on 18-04-2023
The Open/Closed Principle (OCP) is one of the five principles of object-oriented programming and design known as SOLID. The OCP states that a class should be open for extension but closed for modification. In other words, you should be able to add new functionality to a class without modifying its existing code. This principle promotes a more robust and maintainable software design by reducing the likelihood of introducing bugs when adding new features. In this article, we will explore the Open/Closed Principle and demonstrate its application in Ruby.
The OCP revolves around two primary concepts:
- Open for extension: You should be able to add new features or behaviors to a class without affecting its existing functionality.
- Closed for modification: The source code of a class should not need to be modified when adding new functionality.
By adhering to the OCP, you can create a more flexible and maintainable codebase that is less prone to bugs and more adaptable to changing requirements.
Let's illustrate the Open/Closed Principle using a Ruby example. Imagine we have a system that generates reports in different formats, such as HTML and JSON.
We'll use the JSON and Nokogiri gems to create JSON and HTML reports, respectively. First, you'll need to install the gems:
gem install json nokogiri
Here's an initial implementation without considering the OCP:
require 'json' require 'nokogiri' class ReportGenerator def initialize(data) @data = data end def generate(format) case format when :html generate_html when :json generate_json end end private def generate_html builder = Nokogiri::HTML::Builder.new do |doc| doc.html do doc.head { doc.title 'Report' } doc.body do doc.h1 'Report Data' doc.table do doc.tr do data.keys.each { |key| doc.th key } end doc.tr do data.values.each { |value| doc.td value } end end end end end builder.to_html end def generate_json data.to_json end end
The problem with this implementation is that whenever we need to support a new report format, we must modify the ReportGenerator class by adding a new method and updating the generate method. This violates the Open/Closed Principle.
To refactor this code and adhere to the OCP, we can use polymorphism and separate the report generation logic into different classes:
class ReportGenerator def initialize(data) @data = data end def self.generate(data) new(data).generate end def generate raise NotImplementedError, "'generate' method should be implemented" end end
require 'nokogiri' class HTMLReportGenerator < ReportGenerator def generate builder = Nokogiri::HTML::Builder.new do |doc| doc.html do doc.head { doc.title 'Report' } doc.body do doc.h1 'Report Data' doc.table do doc.tr do @data.keys.each { |key| doc.th key } end doc.tr do @data.values.each { |value| doc.td value } end end end end end builder.to_html end end
require 'json' class JSONReportGenerator < ReportGenerator def generate @data.to_json end end
Now, when we need to support a new format, we can simply create a new class that implements the generate method without modifying the existing code:
class XMLReportGenerator < ReportGenerator def generate # add your own code to generate a report in XML format end end
In this example, the HTMLReportGenerator class uses the Nokogiri gem to build an HTML report with a table containing the data. The JSONReportGenerator class uses the JSON gem to convert the data to a JSON-formatted string.
Now you can use the ReportGenerator class to generate reports in different formats:
data = { title: 'Sample Report', views: 100, clicks: 20 } html_report = HTMLReportGenerator.generate(data) puts html_report json_report = JSONReportGenerator.generate(data) puts json_report xml_report = XMLReportGenerator.generate(data) puts xml_report
This refactored code adheres to the Open/Closed Principle by allowing new report formats to be added without modifying the ReportGenerator class.
The Open/Closed Principle is an essential concept in object-oriented programming and design that promotes robust, maintainable, and flexible code. By applying the OCP in your Ruby projects, you can create a more adaptable and resilient codebase that is better prepared to handle changing requirements and less prone to bugs.
The code in the sample has been updated where we no longer inject the subclasses but call the generate method on the subclasses directly.
Coding
Posted on 07 Apr, 2023Coding
Posted on 07 Apr, 2023Coding
Posted on 07 Apr, 2023