Il post di oggi tratta il primo dei pattern comportamentali mostrati dai GoF, la catena delle responsabilità.
Questo pattern prevede una serie di comandi da eseguire ed una serie di oggetti adibiti alla loro applicazione.
Ognuno di questi oggetti "handler" è in grado di inoltrare il comando ad un successivo handler della catena nel caso in cui non abbia le caratteristiche per eseguirlo.
Deve essere dunque implementato il meccanismo per poter linkare questi handler alla coda della catena.
Per mostrare questo pattern, prendiamo come esempio una situazione reale molto nota agli sviluppatori web.
Supponiamo che un manager abbia il compito di consegnare un nuovo progetto web.
Per realizzare l'intero progetto devono essere svolte diverse attività eterogenee, come ad esempio disegnare l'interfaccia grafica, sviluppate l'applicazione, scrivere il manuale utente, deployare l'applicazione.
Il manager non ha queste conoscenze tecniche ma puo' contare su un pool di sviluppatori.
Quando l'attività arriva ad uno sviluppatore, questo puo' risolverla od inoltrarla ad un collega se non è in grado di fronteggiarla.
Si forma in questo modo una catena di responsabilità, in cui ogni attore è specializzato a risolvere solo particolari tipi di richieste.
Vediamo ora come implementare in Ruby questa situazione.
E' necessario dunque che ogni elemento della catena abbia la capacità di "inoltrare" la richiesta al successivo se non è in grado di gestirla.
Creiamo dunque un modulo che definisce questa logica comune in modo da eliminare duplicazioni nel codice.
#models.rb
module Chainable
def next_in_chain(link)
@next = link
end
def method_missing(method, *args, &block)
if @next == nil
puts "This request cannot be handled!"
return
end
@next.__send__(method, *args, &block)
end
end
Come si puo' notare, il metodo next_in_chain stabilisce quale sarà il prossimo elemento della catena.
Per far fronte a richieste che non possono essere gestite, ho usato il "pattern" del method_missing: quando la richiesta non potrà essere gestita dall'attore (i.e. il metodo invocato non è definito nella classe), questo verrà inoltrato al successivo attore.
Definiamo ora gli attori del nostro esempio:
#models.rb
class WebManager
include Chainable
def initialize(link = nil)
next_in_chain(link)
end
def deliver_application
design_interface
build_application
write_documentation
deploy_application
puts "#{self.class.to_s}: Application delivered"
end
end
class WebDeveloper
include Chainable
def initialize(link = nil)
next_in_chain(link)
end
def build_application
puts "#{self.class.to_s}: I'm building the application"
end
def deploy_application
puts "#{self.class.to_s}: I'm deploying the application"
end
end
class WebDesigner
include Chainable
def initialize(link = nil)
next_in_chain(link)
end
def design_interface
puts "#{self.class.to_s}: I'm designing the interface"
end
end
class TechnicalWriter
include Chainable
def initialize(link = nil)
next_in_chain(link)
end
def write_documentation
puts "#{self.class.to_s}: I'm writing the documentation"
end
end
A questo punto simuliamo la nostra catena lanciando il seguente codice:
#main.rb
require 'models.rb'
provider = WebManager.new(WebDeveloper.new(WebDesigner.new(TechnicalWriter.new)))
provider.deliver_application
provider.make_support
L'output fornito sarà:
WebDesigner: I'm designing the interface
WebDeveloper: I'm building the application
TechnicalWriter: I'm writing documentation
WebDeveloper: I'm deploying the application
WebManager: Application delivered
This request cannot be handled!
Concludendo, l'utilizzo di questo pattern risulta utile quando dobbiamo fronteggiare richieste eterogenee e vogliamo fare in modo che queste vengano gestite al meglio dallo specifico handler.
Può anche essere utilizzato quando abbiamo comandi da gestire in successione, in cui ogni elemento inoltra il proseguo dell'azione al successivo.
Una alternativa a questo pattern potrebbe essere l'utilizzo di decorator, in grado di aggiungere potenzialità ad un handler specifico.
In questo caso un singolo handler provvederà a gestire interamente tutte le richieste.
In questi primi tre articoli della serie sui design patterns in Ruby abbiamo analizzato per ogni categoria individuata dai GoF il primo pattern da loro mostrato.
Nei prossimi articoli non seguirò l'ordine previsto dai GoF ma analizzerò i pattern che riterrò più utili per fronteggiare le più comuni necessità.
Codice sorgente "qui":http://github.com/devinterface/design_patterns_in_ruby
Post precedenti della serie:
Design Patterns in Ruby: Introduzione
Design Patterns in Ruby: Abstract Factory
Design Patterns in Ruby: Adapter