Dekorator w Ruby

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
Akcja "Oświeć Przyjaciół", podziel się znaleziskiem:
  • Wykop
  • Facebook
  • Twitter
  • Blip
  • email
  • del.icio.us
  • Blogger.com
  • Google Bookmarks
  • Śledzik
  • RSS
This entry was posted in polskie, programowanie, rails, ruby, technologia. Bookmark the permalink.

8 Responses to Dekorator w Ruby

  1. Andrzej says:

    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?

    • qertoip says:

      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.

      • Andrzej says:

        Nie do konca rozumiem argument z DI.

        Jesli chcesz miec tutaj wiecej zaleznosci to pewnie i klasa ma wiecej odpowiedzialnosci, lamiac SRP.

        • qertoip says:

          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ć.

  2. 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 ?

    • Andrzej says:

      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.

        • Andrzej says:

          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.

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>