Паттерны проектирования в Ruby: Шаблонный метод (Template Method)

3 ноября 2015, вторник

Представьте, что у вас есть некий код, который реализует определенный набор действий. Но есть различные сценарии, в которых определенные куски кода должны работать по-разному. Например, придумаем программу постройки здания. Процесс постройки для зданий одинаковый: заложить фундамент, построить стены, вставить окна, построить крышу, провести коммуникации и т. д. Но для разных зданий все эти действия будут существенно отличаться. В этом случае отлично подойдет Шаблонный метод.

Итак, суть паттерна Шаблонный метод в том, что есть класс определяющий основу алгоритма и позволяющий наследникам переопределять некоторые шаги алгоритма, не изменяя его структуру в целом.

В качеcтве примера будем «строить здания». Для начала определим базовый «абстрактный» класс BaseBuilding, который будет определять скелет алгоритма.

class BaseBuilding

  def report_about_building
    make_preparations
    lay_foundation
    sum_up_communication
    build_walls
    create_windows
    make_roof
    take_out_trash
  end

  protected

  def make_preparations
    raise_error
  end

  def lay_foundation
    p 'Выкопать котлован и залить фундамент'
  end

  def sum_up_communication
    p 'Подвести трубы с водой, электричество, газ'
  end

  def build_walls
    raise_error
  end

  def create_windows
    p 'Поставить окна'
  end

  def make_roof
    raise_error
  end

  def take_out_trash
    p 'Выкинуть мусор'
  end

  private

  def raise_error
    raise StandardError, 'Метод должен быть определен в наследнике'
  end

end

У этого класса есть функция report_about_building, которая содержит последовательность действий постройки, и сами действия постройки. Эти методы я обозначил как protected, тк не планирую вызывать их извне. Есть несколько методов, которые обязательно должны быть реализованы в наследниках, иначе будет выброшено исключение.

Будем строить коттеджи(класс Cottage) и небоскребы(класс Skyscraper).

require_relative 'base_building'
class Cottage < BaseBuilding

  protected

  def make_preparations
    p 'Купить деревянный сруб, приевезти его на место, нанять бригаду рабочих'
  end

  def build_walls
    p 'Сборка деревянного сруба'
  end

  def make_roof
    p 'Сделать каркас крыши, покрыть кровельным материалом'
  end

end

require_relative 'base_building'
class Skyscraper < BaseBuilding

  protected

  def make_preparations
    p 'Заключить договор со стироительной фирмой, пригнать технику и пивезти необходимые материалы, разработать место для постройки'
  end

  def build_walls
    p 'Укладка стен из стекла и бетона'
  end

  def make_roof
    p 'Построить крышу, сделать вертолетную площадку, антенны и тп'
  end

end

Ну и теперь давайте попробуем построить оба эти здания:

require_relative 'cottage'
require_relative 'skyscraper'

cottage = Cottage.new
cottage.report_about_building

=begin
Результат будет такой:
"Купить деревянный сруб, приевезти его на место, нанять бригаду рабочих"
"Выкопать котлован и залить фундамент"
"Подвести трубы с водой, электричество, газ"
"Сборка деревянного сруба"
"Поставить окна"
"Сделать каркас крыши, покрыть кровельным материалом"
"Выкинуть мусор"
=end


skyscraper = Skyscraper.new
skyscraper.report_about_building

=begin
Результат будет такой:
"Заключить договор со стироительной фирмой, пригнать технику и пивезти необходимые материалы, разработать место для постройки"
"Выкопать котлован и залить фундамент"
"Подвести трубы с водой, электричество, газ"
"Укладка стен из стекла и бетона"
"Поставить окна"
"Построить крышу, сделать вертолетную площадку, антенны и тп"
"Выкинуть мусор"
=end