Résumé des patrons de conception expliqués dans le livre Design Patterns in Ruby
View the Project on GitHub armandfardeau/design-patterns-in-ruby
Nous voulons construire un système hautement intégré, c’est-à-dire un système où chaque partie est consciente de l’état de l’ensemble. Nous voulons aussi qu’il soit maintenable, donc nous devrions éviter le couplage entre les classes.
Si nous voulons qu’un composant (observateur) connaisse les activités d’un autre (sujet), nous pourrions simplement câbler les deux classes et informer le premier sur certaines actions effectuées sur le second. Cela signifie que nous devrions passer une référence à l’observateur lorsque nous créons le sujet, et appeler certaines de ses méthodes lorsque ce dernier change. Cependant, dans cette approche, nous faisons quelque chose que nous voulons éviter : augmenter le couplage. De plus, si nous voulions informer un autre observateur, nous devrions modifier la mise en œuvre du sujet pour qu’il le notifie, même si rien n’a changé. Une bien meilleure approche consiste à tenir une liste d’objets intéressés par les changements de sujet et à définir une interface propre entre la source de l’information (le sujet) et les consommateurs (les observateurs). De cette façon, chaque fois qu’il y a un changement sur le sujet, il suffit d’itération sur la liste des observateurs et de les avertir en utilisant l’interface que nous avons définie.
Considérons un objet Employee
qui a un bien salary
. Nous aimerions pouvoir modifier son salaire et tenir le système de paie au courant de ces modifications. La façon la plus simple d’y parvenir est de passer une référence à la paie et de l’informer chaque fois que nous modifions le salary
de l’employé :
class Employee
attr_reader :name, :title
attr_reader :salary
def initialize( name, title, salary, payroll)
@name = name
@title = title
@salary = salary
@payroll = payroll
end
def salary=(new_salary)
@salary = new_salary
@payroll.update(self)
end
end
Le problème est que si nous voulons notifier quelqu’un d’autre (TaxMan
, par exemple), nous devons modifier l’employé. Cela signifie que d’autres classes sont à l’origine des changements apportés à Employee
, même si rien n’y a changé. Fournissons un moyen de garder une liste des objets intéressés sur les changements de salaire.
class Employee
attr_reader :name, :title
attr_reader :salary
def initialize(name, title, salary)
@name = name
@title = title
@salary = salary
@observers = []
end
def salary=(new_salary)
@salary = new_salary
notify_observers
end
def add_observer(observer)
@observers << observer
end
def delete_observer(observer)
@observers.delete(observer)
end
def notify_observers
@observers.each do |observer|
observer.update(self)
end
end
end
Maintenant nous pouvons utiliser la méthode add_observer
pour ajouter autant d’objets que nous voulons à la liste des observateurs, et tous seront notifiés chaque fois que le salaire change.
fred = Employee.new('Fred', 'Crane Operator', 30000.0)
payroll = Payroll.new
fred.add_observer(payroll)
tax_man = TaxMan.new
fred.add_observer(tax_man)
fred.salary=35000.0
Même si nous pouvons implémenter le pattern par nous-mêmes, la bibliothèque standard Ruby possède un module pré-construit qui nous permet de rendre n’importe lequel de nos objets observables, nous libérant ainsi de définir les mêmes méthodes partout. Repenser la classe Employee
pour utiliser le module Observable
:
require 'observer'
class Employee
include Observable
attr_reader :name, :title
attr_reader :salary
def initialize(name, title, salary)
@name = name
@title = title
@salary = salary
end
def salary=(new_salary)
@salary = new_salary
changed
notify_observers(self)
end
end