понедельник, 7 января 2013 г.

Паттерны Ruby. Паттерн стратегия.


Паттерн Шаблонный метод основан на наследовании, что не дает нам быть настолько гибкими, насколько мы хотим. Решением недостатков Шаблонного метода является паттерн стратегия (Strategy).
Почему бы вместо постоянного создания подклассов не изолировать только изменяющиеся кусочки кода в своих отдельных классах.


Диаграмма классов шаблона Стратегия




Пример с тем же самым классом Report:

#-*- encoding: utf-8 -*-
class Formatter
def output_report(title, text)
raise "Abstract method called"
end
end
class HTMLFormatter < Formatter
def output_report(title, text)
puts('<html>')
puts(' <head>')
puts(" <title>#{@title}</title>")
puts(' </head>')
puts(' <body>')
text.each do |line|
puts(" <p>#{line}</p>")
end
puts(' </body>')
puts('</html>')
end
end
class PlainTextFormatter < Formatter
def output_report(title, text)
puts("**** #{title} ****")
text.each do |line|
puts line
end
end
end
class Report
attr_reader :title, :text
attr_accessor :formatter
def initialize(formatter)
@title = 'Отчет за месяц.'
@text = ['Все идет', 'очень хорошо.']
@formatter = formatter
end
def output_report
@formatter.output_report(@title, @text)
end
end
report = Report.new(HTMLFormatter.new)
report.output_report
view raw formatter1.rb hosted with ❤ by GitHub

В данном случае мы передаем "форматировщик" объекту класса Report при его создании. При этом Report знает только то, что форматировщик отзывается на сообщение ouptup_report и вызывает его c аргументами @text и @title. Но что если вместо аргументов передавать контекст, откуда форматировщик будет "вытаскивать" необходимые значения сам? Обратите внимание, что теперь output_report теперь принимает context, в который передается self. Также заметим, что нет реальной необходимости в классе Formatter, так как Ruby является динамически типизированным языком, поэтому создание интерфейсов типа этого является довольно искусственным.
Удалим интерфейс Formatter и перепишем классы с учетом передаваемого контекста:

#-*- encoding: utf-8 -*-
class HTMLFormatter
def output_report(context)
puts('<html>')
puts(' <head>')
puts(" <title>#{context.title}</title>")
puts(' </head>')
puts(' <body>')
context.text.each do |line|
puts(" <p>#{line}</p>")
end
puts(' </body>')
puts('</html>')
end
end
class PlainTextFormatter
def output_report(context)
puts("**** #{context.title} ****")
context.text.each do |line|
puts line
end
end
end
class Report
attr_reader :title, :text
attr_accessor :formatter
def initialize(formatter)
@title = 'Отчет за месяц.'
@text = ['Все идет', 'очень хорошо.']
@formatter = formatter
end
def output_report
@formatter.output_report(self)
end
end
report = Report.new(HTMLFormatter.new)
report.output_report
view raw formatter.rb hosted with ❤ by GitHub
Таким образом, стратегия еще более отделяется от использующего ее класса, но ценой этого является передача большого числа данных через контекст и нет никакой гарантии, что стратегия будет их использовать.


Комментариев нет:

Отправить комментарий