Created by Stan on 18-04-2023
The Dependency Inversion Principle (DIP) is one of the five principles of object-oriented programming and design known as SOLID. It promotes the decoupling of high-level and low-level modules in a system by depending on abstractions rather than concrete implementations. In this article, we will explore the Dependency Inversion Principle and demonstrate how to apply it in a Ruby application.
The Dependency Inversion Principle consists of two key rules:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
We can apply DIP in a Ruby application using interfaces (in the form of abstract base classes or duck typing) or dependency injection. In this article, we'll demonstrate DIP using duck typing and dependency injection.
Consider a scenario where we have a DataExporter
class that exports data to different formats, such as JSON or CSV.
First, we set up two classes, JsonExporter
and CsvExporter
.
require 'json' class JsonExporter def export(data) json_data = data.to_json File.open('data.json', 'w') do |file| file.write(json_data) end puts 'Data exported to data.json' end end
require 'csv' class CsvExporter def export(data) CSV.open('data.csv', 'w') do |csv| data.each do |row| csv << row end end puts 'Data exported to data.csv' end end
Without applying DIP, our DataExporter
class would look like this.
class DataExporter def export_data(data) json_exporter = JsonExporter.new json_exporter.export(data) csv_exporter = CsvExporter.new csv_exporter.export(data) end end
In this example, the DataExporter
class directly creates instances of JsonExporter
and CsvExporter
, making it tightly coupled to those implementations. This violates the DIP and makes the code less flexible and maintainable.
We will now apply DIP to the DataExporter
class using duck-typing and dependency injection.
class DataExporter def initialize(exporters) @exporters = exporters end def export_data(data) @exporters.each do |exporter| exporter.export(data) end end end
By applying DIP, we've decoupled the DataExporter class from the specific implementations of JsonExporter and CsvExporter. The DataExporter class now depends on an abstract concept of an "exporter" rather than concrete implementations. This makes the code more flexible, maintainable, and testable.
To use the new DataExporter, we would inject the dependencies like this:
books = [ { title: 'The Catcher in the Rye', author: 'J.D. Salinger', year: 1951 }, { title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 }, { title: 'Pride and Prejudice', author: 'Jane Austen', year: 1813 }, { title: '1984', author: 'George Orwell', year: 1949 }, { title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', year: 1925 } ] # Map books to CSV data csv_data = books.map { |book| [book[:title], book[:author], book[:year]] } # Add headers for CSV data csv_data.unshift(['Title', 'Author', 'Year']) json_exporter = JsonExporter.new csv_exporter = CsvExporter.new data_exporter = DataExporter.new([json_exporter, csv_exporter]) data_exporter.export_data(books) # JSON export data_exporter.export_data(csv_data) # CSV export
In this example, we've created custom data in the form of an array of hashes representing books. We then convert this data into a CSV-friendly format by creating an array of arrays and adding a header row.
Finally, we initialize the JsonExporter, CsvExporter, and DataExporter instances and use the export_data method to export the custom data in both JSON and CSV formats. The exported files, 'data.json' and 'data.csv', will contain the book data in their respective formats.
Coding
Posted on 07 Apr, 2023Coding
Posted on 07 Apr, 2023Coding
Posted on 07 Apr, 2023