読者です 読者をやめる 読者になる 読者になる

Shred IT!!!!

IT全般について試したこと・勉強したことを綴ったり、趣味について語るブログ

1. Rubyによるデザインパターン【Template Method】

Rubyによるデザインパターン

デザインパターンの勉強は何度もしてきたけど、忘れたり、色々なパターンがごっちゃになったりしてしまう。
勉強不足って一言で片付いてしまうが、結局、理解しきれていないのが問題だと思う。


まずはデザインパターンの根底にあるソウルを受け継いでおきたい。
参考:programming - Rubyによるデザインパターン5原則 - Qiita

  • 変わるものを変わらないものから分離する
  • プログラムはインターフェイスに対して行う(実装に対して行わない)
  • 継承より集約
  • 委譲、委譲、委譲
  • 必要になるまで作るな(YAGNI

プログラマーであれば、デザインパターンを勉強するしないに関わらず、意識した方がいいと思う。

最初は単純だし、よく利用される Template Method(テンプレートメソッド)を実装してみる。


調査

Wikiで調べてみる。
参考:Template Method パターン - Wikipedia

Template Method パターンの目的は、ある処理のおおまかなアルゴリズムをあらかじめ決めておいて、そのアルゴリズムの具体的な設計をサブクラスに任せることである。

Wikiの図にある通り、AbstractClass でおおまかなアルゴリズムを決めて、AbstractClass を継承した ConcreteClass で具象化する。


概念

実装する前にイメージをしてみる。

デート必勝パターンクラス (AbstractClass)
  口説くメソッド (アルゴリズム)
    映画見るメソッド
    バー行くメソッド
  映画見るメソッド
    無計画
  バー行くメソッド
    無計画

A子ちゃん必勝パターンクラス (ConcreteClass) < デート必勝パターンクラス
  映画見るメソッド
    恋愛映画見る (アルゴリズムの具象化)
  バー行くメソッド
    カクテル飲ませる

B子ちゃん必勝パターンクラス < デート必勝パターンクラス
  映画見るメソッド
    サスペンス映画見る
  バー行くメソッド
    ウイスキーロック飲ませる

デート必勝パターンで口説く場合、必ず映画を見てからバーに行くという流れになる。
この必勝パターンのみでは具体的でないので、A子ちゃんとデートするときはA子ちゃん向けに計画を具体化しておく。
これでA子ちゃんを口説くときには、「映画は恋愛映画見に行って、バーに行ったらカクテル飲ませる」ことになる。

これら Template Method のメリットは

  • 必勝パターンの流れ と 具体的な計画 が分離されること
    • 口説く相手が増えても、相手によって「見る映画」と「バーで何を飲ませるか」を考えるだけでいい
    • 逆にA子ちゃんをどうやって口説くか思い出すときにも、「見る映画」と「バーで何を飲ませるか」を意識すればいい

逆にデメリットは

  • 必勝パターンが破綻したときに影響範囲が広い
    • 100人の必勝パターンを作った後に破綻した場合、100人分の必勝パターンを見直さないといけない可能性がある
    • つまり、上記の例だと映画、何を飲ませるかを考え直す必要が出てくるかもしれない
    • 他には、映画とバーに行く順番を逆にしたいとか、映画の前にカフェ行きたい となった場合

実装

いよいよ、具体的な実装をしてみる。

# coding: utf-8

class VictoryPattern
  def move
    movie
    bar
  end
  def movie;end
  def bar;end
end

class APattern < VictoryPattern
  def movie
    p "  恋愛映画を見る"
  end
  def bar
    p "  カクテルを飲ませる"
  end
end

class BPattern < VictoryPattern
  def movie
    p "  サスペンス映画を見る"
  end
  def bar
    p "  ウイスキーロックを飲ませる"
  end
end

class CPattern < VictoryPattern
  def bar
    p "  ジュースを飲ませる"
  end
end

p "A子必勝パターン"
APattern.new.move

p "B子必勝パターン"
BPattern.new.move

p "C子必勝パターン"
CPattern.new.move

# 実行結果
# "A子必勝パターン"
# "  恋愛映画を見る"
# "  カクテルを飲ませる"
# "B子必勝パターン"
# "  サスペンス映画を見る"
# "  ウイスキーロックを飲ませる"
# "C子必勝パターン"
# "  ジュースを飲ませる"

必要最低限な実装で味気ない…

デザインパターンに度々出てくる 抽象化 という言葉。
Ruby では abstract の定義ができないので、厳密には抽象クラスや抽象メソッドを作れないことに注意。

上記の例では、CPattern クラスで movie メソッドを実装していなくても、エラーが起こらない。

例えば、サブクラスで movie や bar メソッドのオーバーライドを強制したい場合、 親クラスのメソッドが呼び出されたら raise すればいい。

class VictoryPattern
  ...
  def movie
    raise "Abstract Method Error!!"
  end
  ...
end

参考:Ruby で 抽象クラス/メソッド を作りたい - Qiita

この実装を入れておくと、上記の CPattern インスタンスの move メソッドは呼び出しに失敗するようになる。
つまり、movie メソッドも実装しろと強制できる。

Rubyの場合、親クラスのメソッドを空の実装にしておくか、raise するかは悩むところ。


まとめ

デザインパターン5原則を意識すると、集約・委譲がよしとされている中、継承の代表格 Template Method を使う場面ってあるのか考えてしまう。

色々と考えを巡らせたけど、Template Method で書くとリーダブルコードになるのでは、と考えた。
超簡易フレームワークとして機能するため、サブクラスは規則正しい実装になりやすいので、コードが読みやすくなるメリットがあると思う。

ちなみにリーダブルコードを学ぶならこの書籍。

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

実際の業務での使いどころは、定常的な処理の流れがありそうな、例えばバッチ処理とか。

起動
  事前処理
  メイン処理
  事後処理

Template Method の実装例でよく出てくるのは、テキストを HTML, XML, JSON などで出し分ける例。
テキストを加工して出力するまでの流れを抽象化して親クラスに切り出し、加工処理は各サブクラスに実装するという、Template Method 利用の素晴らしい例だと思う。

おさらい

Template Method のキモは、継承
なんだか処理が似ているなーってときには、処理の流れのみをクラス切り出して、それを継承したサブクラスで処理の詳細を実装すること。

次回

次は Template Method とほぼ同じ動作をするし、使いどころも似ている Strategy パターンの記事を書こうと思います。


Rubyによるデザインパターン ←この本を持っていないので、下記サイトを参考にさせて頂きました。