Qiita のデザインパターンまとめがおもしろかった

qiita.comおもしろかったので自己流にメモしておく。
 

Template Method

日記を書いて、複数のフォーマットで出力することを考える。日記の内容は、タイトルと何行かの本文、フォーマットは HTML とプレーンテキスト。

class Diary
  def initialize(title, text)
    @title = title
    @text = text
  end
  
  def output_diary
    output_title
    output_body_start
    output_body
    output_body_end
    puts
  end

  def output_title
    raise 'Called abstract method: output_title'
  end

  def output_body
    raise 'Called abstract method: output_body'
  end

  def output_body_start
  end

  def output_body_end
  end
end

class HTMLReport < Diary
  def output_title
    puts "<title>#{@title}</title>"
  end

  def output_body
    @text.each do |line|
      puts "<p>#{line}</p>"
    end
  end

  def output_body_start
    puts '<body>'
  end

  def output_body_end
    puts '</body>'
  end
end

class PlainReport < Diary
  def output_title
    puts "***#{@title}***"
  end

  def output_body
    @text.each do |line|
      puts "***#{line}***"
    end
  end
end


title = "2018/9/13"
text = ["今日は本屋へ行った。", "夜、デザインパターンのお勉強がおもしろかった。"]

diary1 = HTMLReport.new(title, text)
diary1.output_diary

diary2 = PlainReport.new(title, text)
diary2.output_diary

継承を使う。Diary クラス内の空のメソッドは、PlainText では使わないのでこうなっている。これを「フック・メソッド」という。もちろん、共通の処理があればここで処理すればよい。
結果。

<title>2018/9/13</title>
<body>
<p>今日は本屋へ行った。</p>
<p>夜、デザインパターンのお勉強がおもしろかった。</p>
</body>

***2018/9/13***
***今日は本屋へ行った。***
***夜、デザインパターンのお勉強がおもしろかった。***

 

Strategy

Template Method は、同じレポートなのにフォーマットごとにちがったオブジェクトを作らねばならない。なので、フォーマットを表すクラスを使えば、Diary オブジェクトは同じで済む。

class Diary
  attr_reader :title, :text
  attr_writer :formatter    #集約

  def initialize(title, text, formatter)
    @title = title
    @text = text
    @formatter = formatter
  end

  def output_diary
    @formatter.output(self)     #委譲
  end
end

class HTMLFormatter
  def output(report)
    puts "<title>#{report.title}</title>"
    puts '<body>'
    report.text.each do |line|
      puts "<p>#{line}</p>"
    end
    print "</body>\n\n"
  end
end

class PlainTextFormatter
  def output(report)
    puts "***#{report.title}***"
    report.text.each do |line|
      puts "***#{line}***"
    end
    puts
  end
end


title = "2018/9/13"
text = ["今日は本屋へ行った。", "夜、デザインパターンのお勉強がおもしろかった。"]

diary = Diary.new(title, text, HTMLFormatter.new)
diary.output_diary

diary.formatter = PlainTextFormatter.new
diary.output_diary

レポートを表すオブジェクトはひとつ(diary)で済んでいる。また、フォーマットのクラスは共通の output メソッドをもっていて、Ruby 流のダッグタイピングを使っている。
 

Observer

オブジェクトの状態が変ったら、他所(Observer)へ通知する。通知する場所は自由に追加、削除できる。

class Man
  def initialize(name)
    @name = name
    @observers = []
  end
  
  def wake_up(time)
    @time = time
    notify_observers
  end
  attr_reader :name, :time
  
  # それぞれのオブザーバーに変更を通知
  def notify_observers
    @observers.each do |observer|
      observer.update(self)
    end
  end

  # オブザーバーの追加
  def add_observer(observer)
    @observers << observer
  end

  # オブザーバーの削除
  def delete_observer(observer)
    @observers.delete(observer)
  end
end

class School
  def update(man)
    puts "#{man.name}ですが、#{man.time}に起きました。いまから行きます。"
  end
end

class NewYork
  def update(man)
    puts "Good morning (?) from #{man.name}. Current time is #{man.time} in Kyoto."
  end
end


tomoki = Man.new("Tomoki")

tomoki.add_observer(School.new)
tomoki.add_observer(NewYork.new)

tomoki.wake_up("8:00")

目が覚めたときに Observer(School と NewYork)に現在時刻と名前を通知する。Observer は共通の update メソッドをもっていればよい。ここで通知を受けたときの処理をする。
結果。

Tomokiですが、8:00に起きました。いまから行きます。
Good morning (?) from Tomoki. Current time is 8:00 in Kyoto.

 

Composite

部品から全体を組み立てる。その中で、一日の外出時間を計算してみる。

class Task
  attr_reader :name
  def initialize(name)
    @name = name
  end

  # 所要時間を返すメソッド
  def get_time_required
    0
  end
end

class CompositeTask < Task
  def initialize(name)
    super(name)
    @sub_tasks = []
  end

  def add_sub_task(task)
    @sub_tasks << task
  end

  def remove_sub_task(task)
    @sub_tasks.delete(task)
  end

  def get_time_required
    @sub_tasks.inject(0) {|time, task| time + task.get_time_required}
  end
end


class Library < Task
  def initialize
    super("図書館")
  end
  
  def get_time_required
    20
  end
end

class BookStore < Task
  def initialize
    super("本屋")
  end
  
  def get_time_required
    15
  end
end

class MisterDonut < Task
  def initialize
    super("ミスタードーナツ")
  end
  
  def get_time_required
    10
  end
end

class ReadBook < Task
  def initialize
    super("読書")
  end
  
  def get_time_required
    45
  end
end

class FoodCourt < CompositeTask
  def initialize
    super("フードコート")
    add_sub_task(MisterDonut.new)
    add_sub_task(ReadBook.new)
  end
end

class AEON < CompositeTask
  def initialize
    super("イオンモール")
    add_sub_task(BookStore.new)
    add_sub_task(FoodCourt.new)
  end
end

class OneDay < CompositeTask
  def initialize
    super("今日一日")
    add_sub_task(Library.new)
    add_sub_task(AEON.new)
  end
end

puts OneDay.new.get_time_required    #=>90

Task クラスがいちばん小さい単位。それを CompositeTask クラスで組み立てるだけ。Task クラスを継承しているものを「葉」、CompositeTask クラスを継承しているものを「コンポジット」という。get_time_required メソッドが共通である。それぞれのクラスでは独自のメソッドを他にもってもよい。