抽象的にアルゴリズムを記述できるTemplate Methodパターン

 次はTemplate Methodパターンを見てみましょう。再び『デザインパターン』の解説を引用すると,Template Methodパターンは「1つのオペレーションにアルゴリズムのスケルトンを定義しておき,その中のいくつかのステップについては,サブクラスでの定義に任せることにする。Template Methodパターンでは,アルゴリズムの構造を変えずに,アルゴリズム中のあるステップをサブクラスで定義する」とあります。

 これは実はオブジェクト指向プログラミングでは,継承を用いて一般的に使われている技法です。

 例としてRubyのpメソッドを考えてみましょう。pメソッドはデバッグ用にオブジェクトの情報を表示するメソッドです。デバッグ情報を出力するアルゴリズムとは,要するに

(1)オブジェクトのデバッグ用出力情報を文字列として用意し

(2)putsメソッドで出力する

ことです。あまりにも単純すぎてアルゴリズムと呼ぶのもおこがましい気がしますが,これがデバッグ出力です。

 しかし,実際にデバッグ情報を出力するためには,各種オブジェクトに応じてデバッグ用出力文字列を用意するという点で意外に大変です。クラスの種別ごとに場合分けをしていては大変ですし,新たなクラスが追加されたときに対応するコストが膨大になります。

 こういう時に使えるのが,Template Methodパターンです。Template Methodパターンを用いて,デバッグ出力メソッドpを記述するなら,次のようになるでしょう。意外なほど簡単です。

def p(obj)
puts obj.inspect
end

 この単純なメソッドは,(1)と(2)の「アルゴリズム」をそのまま書き下ろしただけです。この定義では,各種オブジェクトに固有のデバッグ情報を用意する部分は,それらのオブジェクトのinspectメソッドが用意します。新しいクラスを用意した場合には,そのクラスに適したinspectメソッドを用意することで,pメソッドを使っていつでも適切なデバッグ出力を得られます。

 このように細部を隠したメソッドを呼び出すことで抽象化したアルゴリズムを記述する,そして,細部はサブクラスで提供するのがTemplate Methodパターンです。

RubyでTemplate Methodを試す

 Rubyのクラス・ライブラリでTemplate Methodパターンを最も活用している部分は,EnumerableモジュールとComparableモジュールでしょう。

 本連載でも何度か解説しているEnumerableモジュールは,eachメソッドによる繰り返しをベースにしたTemplate Methodパターンを採用しています。第7回で紹介したEnumerableモジュールのメソッド一覧を表1に再掲します。

表1●Enumerableが提供するメソッド
表1●Enumerableが提供するメソッド

 これらのメソッドすべてがeachメソッドにのみ依存して定義されています。ですから,ユーザー定義のクラスでもeachメソッドだけを用意して,Enumerableモジュールをincludeすればこれら32個のメソッドが提供されます。

 実際のEnumerableモジュールはCで定義されていますが,Rubyで同等のものを定義するのも簡単ですから,Rubyでの定義を見ながら,どのようにTemplate Methodパターンを使えばよいのか考えてみることにしましょう*2

 最も実装が簡単なメソッドの1つは,要素をすべて集めるentriesでしょう。entriesメソッドの実装を図3に示します。図3を見れば分かるように処理内容は簡単です。

def entries
 result = []
 self.each {|elem|
  result << elem
 }
 return result
end
図3●entriesメソッドのRubyによる実装

(1)配列を用意し

(2)eachで各要素を取り出し

(3)配列に追加

(4)最後に配列を返す

 このメソッドは自らの仕事のアウトラインを定義しており,個別のオブジェクトに対応した処理はeachメソッドにより提供されていことが分かります。

 このようなTemplate Methodパターンの使い方は,Comparableモジュールでも同じです。

 Comparableモジュールは大小比較の基本となる「<=>」メソッドを用いて,各種比較演算を提供しています。「<=>」メソッドの仕様はレシーバと引数を比較して,レシーバの方が大きければ正の整数,等しければゼロ,小さければ負の整数を返すというものです。このメソッドを基礎にして,Comparableモジュールは「==」,「>」,「>=」,「<」,「<=」,「between?」の6つの比較演算を提供しています。

 Comparableが提供する比較演算処理の代表として「>」メソッドの実装を見てみましょう(図4)。実際の「>」メソッドではもう少しエラー処理が加わっていますが,基本的な処理は図4の通りです。

def >(other)
 cmp = self <=> other
 if cmp > 0
  return true
 else
  return false
 end
end
図4●「>」メソッドのRubyによる実装

 このようにTemplate Methodパターンを使うと,さまざまなデータ構造の詳細に立ち入ることなく,抽象的なレベルでアルゴリズムを記述できます。つまり,抽象レベルが高い状態でアルゴリズムを記述できるので,同じコードをさまざまな局面に適用できるのです。

 このようにコードの重複を避けることはDRY原則*3の観点からも優れています。

動的言語とTemplate Method

 一般にTemplate Methodは継承とセットで語られますが,Enumerableのようにインクルードするだけで継承関係に関わりなく任意のクラスに機能を追加できるのは魅力的です。もっとも,Rubyのインクルードは一種の(制限された)多重継承なので,何の不思議もありませんが。

 Template Methodパターンのこのような優れた性質は言語が静的であるか否かには関係ありません。Javaのような静的な型を持ち,かつ多重継承を許さない言語では直接の継承関係を強制されます。そのため,Enumerableのようなさまざまなクラスで利用できるアルゴリズム集のようなものをTemplate Methodパターンを使って実現しにくい(interfaceと委譲を組み合わせることで不可能ではない)のですが,それは静的言語の問題ではありません。

 しかし,IoやRubyのようなプロトタイプ・ベース・プログラミングもこなす言語では,もう一歩進んで,特定のオブジェクトにアルゴリズム集を追加できます。やや人工的な例ですが,特定のオブジェクトにEnumerableの機能を追加した例を示します(図5)。

# オブジェクトを1つ作る
dice = Object.new
# eachメソッドを定義
def dice.each

 # とりあえず10回サイコロを振る
 10.times do
  yield rand(6)+1
 end
end
# diceにEnumerableの機能を追加
dice.extend(Enumerable)

# 継承したrejectで3以下の目を排除
p dice.reject{|x| x<=3}
図5●特定のオブジェクトにEnumerableの機能を追加した例

 クラスをどこにも定義していないのに,extendによってdiceオブジェクトでEnumerableモジュールの機能が利用できるようになりました。extendや特異メソッドのような特定のオブジェクトに機能を追加する機能は,先月学んだSingletonパターンを実現するためにも活用できます。

この先は会員の登録が必要です。今なら有料会員(月額プラン)は12月末まで無料!

日経 xTECHには有料記事(有料会員向けまたは定期購読者向け)、無料記事(登録会員向け)、フリー記事(誰でも閲覧可能)があります。有料記事でも、登録会員向け配信期間は登録会員への登録が必要な場合があります。有料会員と登録会員に関するFAQはこちら