Dekorator przyjmuje w konstruktorze obiekt i dodaje do niego nowe zachowania. Przykładem dekoratora jest Presenter – klasa dodająca do obiektu możliwość zaprezentowania się, np. w postaci sformatowanego Stringa, XML-a czy JSON-a. Na wroc_lover.rb Piotr Szotkowski pokazał bardzo fajny sposób tworzenia dekoratorów z użyciem SimpleDelegator. Zysk polega na uniknięciu boilerplate kodu – wszystkie metody dostępne na wejściowym obiekcie stają się dostępne bezpośrednio w dekoratorze.
require 'delegate'
class Address
# extend with your favourite persistence solution or:
attr_accessor :street, :postcode, :city, :country
def initialize args = {}
args.each { |key, value| send "#{key}=", value }
end
end
class PolishAddress < SimpleDelegator
def initialize address
super address
end
def formatted
[street, "#{postcode} #{city}", country].join "\n"
end
end
Czyli mamy i ładnie (SRP), i zwięźle (no boilerplate code). Jeśli natomiast potrzebujemy Presentera bazującego na więcej niż jednym obiekcie, z pomocą przyjdzie mixin Forwardable, również z stdlib:
require 'forwardable'
class PolishEnvelopeAddress
extend Forwardable
def initialize args
@person = args.fetch :person, Person.new
@address = args.fetch :address, Address.new
end
def_delegators :@person, :given_names, :surname
def_delegators :@address, :street, :postcode, :city, :country
def formatted
["Sz.P. #{given_names} #{surname}",
street, "#{postcode} #{city}", country].join "\n"
end
end



When in doubt, hate.
Cos mi sie w tym nie podoba. Nie lepiej zrobic to the-DCI-way? W tym use-casie, gdzie potrzebne jest “polskie” zachowanie wstrzyknac role?
Ciekawy pomysł ale nie jestem do niego przekonany.
Klasa jest otwarta na DI, moduł nie bardzo. Co jeśli chciałbym w testach dekoratora użyć innych zależności (stubów/mocków) niż normalnie?
W tym prostym przykładzie nie jest to namacalne ale potrafię sobie wyobrazić bardziej złożone dekoratory i pewnie chciałbym je testować w izolacji.
Nie do konca rozumiem argument z DI.
Jesli chcesz miec tutaj wiecej zaleznosci to pewnie i klasa ma wiecej odpowiedzialnosci, lamiac SRP.
To teraz ja nie rozumiem ;-)
Każda klasa ma jakieś zależności (choćby z biblioteki standardowej). Nie rozumiem w jaki sposób z posiadania zależności wynika od razu, że klasa łamie SRP.
Nie mam pod ręką dobrego przykładu, więc nieco sztuczny: prezenter produkcyjny wypluwa JSON-a zminifikowanego, natomiast prezenter testowy ładnie sformatowanego dla wygody programisty. Prezentera trzeba zatem skonfigurować.
Andrzeju, jakie rozwiązanie proponujesz jeśli potrzebujesz jednocześnie polski i niemiecki adres dla danego obiektu ? extend jeśli dobrze kojarzę zapamięta metodę z ostatniego dodanego modułu. Czy proponujesz extendować modułami które różne nazwy metod miałyby dodawać ? polish_formatted() etc ?
Milordzie,
Jaki masz use-case na potrzebe uzycia jednoczesnie adresu w stylu polskim i niemieckim? konwencje nazewnicze to na pewno hack’i na taki problem.
Hyper/J mial na to ciekawe rozwiazanie. Tam podczas “merge’owania” mowilo sie, ze metoda foo w tej roli, to tak naprawde bar w tym obiekcie.
Moznaby to zrobic w stylu:
object.extend(PolishEnvelopeAddress, :nicely_formatted => :formatted)
ale to chyba niezwiazane juz z tematem posta ;)
Eksportuje dane do poczty, która będzie musiała rozesłać zaproszenia do różnych krajów i w 1 eksporcie/batchu chce mieć różne kraje bo to np taniej niż 2 batche zlecać poczcie. ? Urojony trochę problem, wiem, aczkolwiek pewnym jego rozwiązaniem byłoby posiadanie 2 obiektów o 2 różnych extendach.
Ciezko mi sie wczuc w ten problem.
Moze rozwiazac to tak, ze najpierw prezentujemy (extendujemy odpowiednio obiekt) wersje polska, potem inny “kontekst” i prezentujemy wersje niemiecka.
Really instructive and fantastic anatomical structure of content material , now that’s user friendly (:.