Паттерн Шаблонный метод основан на наследовании, что не дает нам быть настолько гибкими, насколько мы хотим. Решением недостатков Шаблонного метода является паттерн стратегия (Strategy).
Почему бы вместо постоянного создания подклассов не изолировать только изменяющиеся кусочки кода в своих отдельных классах.
Диаграмма классов шаблона Стратегия
Пример с тем же самым классом Report:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#-*- 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 |
В данном случае мы передаем "форматировщик" объекту класса Report при его создании. При этом Report знает только то, что форматировщик отзывается на сообщение ouptup_report и вызывает его c аргументами @text и @title. Но что если вместо аргументов передавать контекст, откуда форматировщик будет "вытаскивать" необходимые значения сам? Обратите внимание, что теперь output_report теперь принимает context, в который передается self. Также заметим, что нет реальной необходимости в классе Formatter, так как Ruby является динамически типизированным языком, поэтому создание интерфейсов типа этого является довольно искусственным.
Удалим интерфейс Formatter и перепишем классы с учетом передаваемого контекста:
Удалим интерфейс Formatter и перепишем классы с учетом передаваемого контекста:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#-*- 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 |
Таким образом, стратегия еще более отделяется от использующего ее класса, но ценой этого является передача большого числа данных через контекст и нет никакой гарантии, что стратегия будет их использовать.
Комментариев нет:
Отправить комментарий