はじめまして。ランサーズのエンジニアのtomohiroです。
先日、社内勉強会をする事になり、そのお題としてデザインパターンをやったので、内容を少し共有しようと思います。自分はデザインパターンをあんまり勉強していなかったので、この機会にデザインパターンに触れてみようと思いました。そこでお題を「(自分も)デザインパターン入門」に。ソフトウェアでデザインパターンを初めて取り上げたと言われる Gang of Four(GoF) の書籍「オブジェクト指向における再利用のためのデザインパターン」で紹介されている23種類のパターンの内から2つピックアップして、rubyで実装してみる事にしました。
そもそもデザインパターンとは?
ソフトウェアの開発で言うデザインパターンとは、開発中に頻繁に直面する問題に対する解決策をパターン化し、名前をつけた物。更に言うとノウハウを蓄積し、再利用しやすいように定義付けをしたパターンになります。単純にノウハウを貯めるだけではなくノウハウに名前が付くため、議論もしやすくなり、結果的に意思の疎通も容易になります。
ただし、デザインパターンは銀の弾ではありません。あくまでも過去の設計者達が気づいた設計パターンをまとめた物に過ぎません。そのためデザインパターンを覚えれば何でも開発出来るようになるわけでも無ければ、給料があがるわけでもありません。技術者としての知見と技術的引き出しが広がり、結果的にスピードを持って開発が出来るようになり、給料があがる事はありえます。
知らず知らずの内に誰かが定義したデザインパターンを使っている事も多々あります。逆に言語やフレームワークによっては使用が推奨されないパターンも存在します。結局、コードの書き方はある程度経験によって左右され、最終的には適材適所。引き出しを多く持っている事に越した事はないので、歴史から学び、ひとつでも多くの適材適所を増やせるようデザインパターンを知識に取り入れると良い事があるのではないでしょうか。
上で紹介した著書の名前にもあるように、オブジェクト指向に大きく関わるところもあるのですが、今回はそこには深く踏み込みません。今回は「あ、こんなコード見た事ある」って気付きを得るところから始めようかと思います。
コードを見てみましょう
早速ですが、今回ピックアップしたふたつのパターンは Template(テンプレート) と Strategy(ストラテジー) です。似ているようで異なるパターンだったので、ちょうどいいかなと思い取り上げてみました。
日本語に訳すと、
テンプレート → 型紙
ストラテジー → 戦略
パターン的にいうと
テンプレート → ある型紙をベースに処理を書き、必要に応じて処理の変更する
ストラテジー → 処理の流れを指定し、ステップ毎に内容を選択出来るようにする
よくある、車の例で説明すると、
テンプレート → 基本的な車の定義がされていて、それに変更等を加える事によって新しい物を作って、新しい車の定義を作る
(1)その複製を作る(継承)
(2)必要に応じてパーツを付け替えたり、改造したりする
ストラテジー → 車のシャーシが用意されていて、各パーツに何を当てはめるか指定していく。必要に応じてリアルタイムで変更が可能(例:タイヤを付け替える)
(1)シャーシとパーツを用意する
(2)パーツを付け替えて、必要なスペックにする
重要なポイントは、処理の定義を自ら持つのか(テンプレート)、処理流れだけを持ち、処理の定義自体は外のオブジェクトに移譲するのか(ストラテジー)だと思っています。
では実際にコードで説明してみます。
基本的にrubyを多く使ってきているので、rubyで書いています。
今回は定型文のレポートを作成するスクリプトを題材にしています。(Design Patterns in Ruby という本を読んでいたので、それにかなりインスパイアされていると思います。題材もそうだったかも ^^;)
当初はテキストレポートのみ必要だったのが、途中からHTMLレポートも必要だと言われ、レポート自動生成スクリプトを作る事にしたという想定です。
まずはかなり単純なスクリプトを書いてみました。
# -*- coding:utf-8 -*- # # class Report def initialize(opt = {}) @title = opt[:title] @data = opt[:data] end def output_report puts "==============================" puts "#{@title}" puts "==============================" puts "------------------------------" @data.each do |d| puts d end puts "------------------------------" puts "EOF" end end report = Report.new({ title: 'Report 2014-06', data: ['りんご', 'ごりら', 'らっぱ', 'パイソン'] }) report.output_report
単純にputsを並べているだけですね。もしHTMLレポートを作りたければ全部書き換えればいい話しです。今あるクラスをコピペして、必要な部分を書き換える。
=========================================================
次にtemplateパターン
# -*- coding:utf-8 -*-
#
#
class Report
def initialize(opt = {})
@title = opt[:title]
@data = opt[:data]
end
def output_report
pre_document
body_start
header
table_start
table_data
table_end
body_end
post_document
end
def pre_document
# raise “abstract method: #{__method__}”
end
def body_start
# raise “abstract method: #{__method__}”
end
def header
raise “abstract method: #{__method__}”
end
def table_start
raise “abstract method: #{__method__}”
end
def table_data
raise “abstract method: #{__method__}”
end
def table_end
raise “abstract method: #{__method__}”
end
def body_end
# raise “abstract method: #{__method__}”
end
def post_document
raise “abstract method: #{__method__}”
end
end
class HtmlReport < Report def pre_document puts "" end def body_start puts "
”end
def header
puts “
#{@title}
”
end
def table_start
puts “
#{d} |
”
end
def body_end
puts “”
end
def post_document
puts “