Created by Stan on 18-04-2023
Inversion of Control (IoC) is a software design principle that promotes the decoupling of dependencies in a system, leading to more modular, maintainable, and testable code. IoC is also known as the Hollywood Principle, due to its motto: "Don't call us; we'll call you." This article will discuss the concept of IoC and demonstrate how to implement it in a Ruby application.
In a traditional software design, components directly call other components or create instances of dependencies, leading to a tightly coupled system. This tight coupling can make it challenging to modify, maintain, and test the application.
Inversion of Control addresses this issue by inverting the control flow, such that the dependencies are provided to a component, rather than the component creating or acquiring them. This results in a more modular and decoupled system, which is easier to maintain and test.
There are various ways to achieve IoC in a Ruby application, such as using dependency injection, service locators, or IoC containers. In this article, we'll focus on dependency injection as a way to demonstrate IoC in Ruby.
Dependency Injection (DI) is a technique to achieve IoC by providing dependencies to a component through its constructor, properties, or methods. It allows for better separation of concerns and makes testing easier by enabling the substitution of dependencies with test doubles.
Let's consider an example where a Notifier class is responsible for sending notifications via email or SMS.
We can use the mail
gem for sending emails and the twilio-ruby
gem for sending SMS messages. First, you'll need to install these gems:
gem install mail twilio-ruby
Without IoC:
require 'mail' class EmailService def send_email(to, message) # Code to send an email end end
require 'twilio-ruby' class SmsService def send_sms(to, message) # Code to send an SMS end end
class Notifier def initialize @email_service = EmailService.new @sms_service = SmsService.new end def send_notification(to, message) @email_service.send_email(to, message) @sms_service.send_sms(to, message) end end
In the above code, the Notifier
class directly creates instances of EmailService
and SmsService
, making it tightly coupled to those implementations. This tight coupling makes it difficult to modify, maintain, or test the Notifier
class.
Applying IoC using Dependency Injection:
require 'mail' class EmailService def send_email(to, message) # Code to send an email end end
require 'twilio-ruby' class SmsService def send_sms(to, message) # Code to send an SMS end end
class Notifier def initialize(email_service, sms_service) @email_service = email_service @sms_service = sms_service end def send_notification(to, message) @email_service.send_email(to, message) @sms_service.send_sms(to, message) end end
By applying IoC through dependency injection, we've decoupled the Notifier class from the specific implementations of EmailService
and SmsService
. Now, the Notifier
class no longer needs to know how to create these services, and we can easily substitute them with alternative implementations or test doubles.
Inversion of Control can be applied to various real-world scenarios to improve the design of an application. Some common use cases include:
Database access: Decoupling components from specific database implementations allows for easy switching between different database systems or connection strategies.
Third-party services: By decoupling components from specific third-party service implementations, it becomes easier to switch between providers or adapt to API changes.
Logging and error handling: IoC can be used to decouple components from specific logging or error handling strategies, allowing for more flexible and configurable logging/error handling in an application.
While IoC can be implemented manually, several Ruby libraries can help simplify the process:
dry-rb: A collection of Ruby libraries that includes dry-container and dry-auto_inject for dependency injection and managing IoC containers.
light-service: A library for organizing Ruby code into a series of actions, with explicit dependencies passed between them.
injectable: A minimalistic dependency injection library for Ruby.
Coding
Posted on 07 Apr, 2023Coding
Posted on 07 Apr, 2023Coding
Posted on 07 Apr, 2023