Spionieren mit Ruby
TL;DR:
Ich habe gerade etwas darüber nachgedacht, warum Ruby keine Interfaces, wie in anderen Sprachen üblich, enthält. In der UML gibt es eine spezielle Notation dazu (siehe UML Superstructure, Kap 7.3.24) und in Klassendiagrammen werden diese auch öfter verwendet. Nun wollte ich ein solches (Subject/Observer-Pattern) in Ruby umsetzen, doch ohne Interfaces muss man einen anderen Weg einschlagen.
Im IRC (#ruby-lang) wurde mir zu Modulen bzw. Mixins geraten. Eine Technik, die einer Abstrakten Klasse sehr nahe kommt, nur das man damit konkrete Klassen erweitern kann. Um dies bei dem Design-Pattern anzuwenden, habe ich folgendes Klassendiagramm erstellt:
Das Diagramm ist nicht vollständig UML-Konform, da Mixins nirgends vorgesehen sind.
Die Implementierung in Ruby gestaltet sich (wie so oft) recht einfach. Zuerst das Modul Observer
: Dieser enthält nur den Konstruktor, der das Subjekt in einem Attribut speichert.
module Observer
def initialize(s)
@subject = s
end
def update
end
end
Das Subject
-Modul übernimmt hier die meiste Arbeit: Der Konstruktor initialisiert ein neues Array, welches alle Observers sammelt. Die attach() und detach() übernehmen hier die Verwaltung dieses Arrays. Die notify()-Methode ruft von allen eingetragenen Observers die update()-Methode auf.
module Subject
def initialize
@observers = Array.new
end
def attach(observer)
@observers << observer
end
def detach(observer)
@observers.delete(observer)
end
def notify
@observers.each { |view| view.update }
end
end
Eine konkrete Implementierung würde so aussehen:
class Thing
include Subject
def shit_happens
@stuff = "Plop"
notify
end
end
# Prints the length of the string
class View1
include Observer
def update
puts @subject.stuff.length
end
end
# Do some Stuff with the string
class View2
include Observer
def update
puts @subject.stuff.reverse.capitalize
end
end
Zum Schluss ein kleiner Test:
a = Thing.new
v1 = View1.new(a)
v2 = View2.new(a)
a.attach(v1)
a.attach(v2)
a.shit_happens
a.detach(v1)
a.shit_happens
Dies würde folgende Ausgabe erstellen:
4
Polp
Polp
Ruby ermöglicht es, sauberen Code zu schreiben, auch ohne Interfaces.