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 メソッドが共通である。それぞれのクラスでは独自のメソッドを他にもってもよい。