IOC & DI en Ruby
🎯 Objectif ; mettre en place un découplage fort et éviter l'effet "boite à outils"
Cas d'étude, l'anti-pattern
Un composant quelconque doit logger ses actions; l'anti-pattern pour :
class Composant
def initialize
@log = open('composant.log', 'a')
end
def action
# a process
puts 'action made'
@log.puts "action made"
end
end
Constat :
- ❌ le log est instancié directement
- ❌ le log est implémenté dans le composant
Un pas en avant loin d'être suffisant
Ne pas implémenter le Logger, mais l'utiliser, Utiliser la solution dédiée et sortir du composant "boite à outils"
class Composant
def initialize
@log = Logger.new('logfile.log')
end
def action
# a process
puts 'action made'
@log.puts "action made"
end
end
Constat :
- ❌ le log est toujours instancié directement
- ✅ le log n'est plus implémenté dans le composant
Vers le pattern
class Composant
attr :log
def initialize(log: Logger.new('logfile.log'))
@log = log
end
def action
# a process
puts 'action made'
log.info 'action made'
end
end
Constat :
- ✅ le log n'est plus instancié directement dans le composant et devient substituable
- ✅ le log n'est plus implémenté dans le composant
On vient de réaliser une réduction du couplage et une inversion de contrôle
L'approche injection de dépendance
Un gem qui implémente la DI de façon simple :
require 'rubygems'
require 'micon'
require 'logger'
micon.register(:logger){Logger.new STDOUT}
class Application
inject :logger
def run
logger.info 'running ...' if Application.respond_to? :logger
end
end
class Application2
inject :logger
def run
logger.info 'running twice...'
end
end
Application.new.run
Application2.new.run
On voit en plus que l'injection permet de proposer un accès à un unique à un service pré-instancié
Pour aller plus loin
Pour aller plus loin on peu utiliser un container leger avec une registry de services injectable :