概要
「Rubyによるデザインパターン※」を読んでデザインパターンを勉強中。
Javaをやっていた人間としての目線で情報を整理してみます。
※残念ながら日本語版は絶版らしいですが原著はKindleで購入可能です。結構文章も読みやすいので極端な英語アレルギーがある人でなければオススメです。
Template Method Pattern
- 「変わらないもの」と「変わるもの」を親クラスと子クラスに分離する
- 親クラスでは処理の流れ(変わらないもの)のみを定義する
- 子クラスでは具体的な処理(変わるもの)を実装する
AbstractClass#template_method 内では抽象メソッド(operation1, operation2, operation3)の呼び出し順など、処理の骨格のみを定義。operation1〜operation3 の具体的な処理内容は子クラスで実装することによって、実装の変更に対する影響を局所化できる。
Java での実装
public abstract class AbstractClass {
abstract protected void operation1();
abstract protected void operation2();
abstract protected void operation3();
public void templateMethod() {
operation1();
operation2();
operation3();
}
}
public class ConcreteClass extends AbstractClass {
protected void operation1() {
System.out.println("This is op 1.");
}
protected void operation2() {
System.out.println("This is op 2.");
}
protected void operation3() {
System.out.println("This is op 3.");
}
}
Ruby での実装
Java の場合は親クラスで抽象メソッドとして定義したメソッドを子クラスでオーバーライドすることを強制できるが、Rubyにはそのような仕組みは存在しない。
そのため、子クラスでメソッドがオーバーライドされることを保証するために、親クラスでは例外を投げるようにする、という方法をとる。
class AbstractClass
def operation1
raise "Must override method :operation1"
end
def operation2
raise "Must override method :operation2"
end
def operation3
raise "Must override method :operation3"
end
def template_method
operation1
operation2
operation3
end
end
class ConcreteClass < AbstractClass
def operation1
puts "This is op 1."
end
def operation2
puts "This is op 2."
end
# operation3 の実装漏れ
end
この場合、ConcreteClass.new.template_method
のように実行すると次のように 実行時例外 が発生する。
This is op 1.
This is op 2.
test.rb:11:in `operation3': Must override method :operation3 (RuntimeError)
from test.rb:17:in `template_method'
from test.rb:32:in `<main>'
もちろん実行してみるまでエラーは発見できませんが、それを確認できるようなユニットテストを行えば良い。つまり、Java ではコンパイル時に検証されていた部分をユニットテストにて検証するのです。
Sample Code
Abstract Class
class Worker
def initialize
@output = 0
end
# template method. 9時から18時まで仕事をしてその日の進捗を上司に報告します
# :do_task の実装方法が変更されてもこのメソッド自体には影響がない
def work
for hour in 9...12
hourly_log time: hour, memo: do_task()
end
hourly_log time: 12, memo: lunch()
for hour in 13...18
hourly_log time: hour, memo: do_task()
end
report
end
private
# 抽象メソッド. 子クラスでオーバーライドします
def do_task
raise "Must override method :do_task"
end
# デフォルト実装を親クラスで定義しておいて、オーバーライドするかどうかを子クラスに任せることも
def lunch
"Lunch with colleagues."
end
def report
puts
puts "Hi boss! I'm #{self.class}."
puts "Today I have finished #{@output} tasks!"
puts
end
def hourly_log(time: , memo: )
printf("%02d:00 %s\n", time, memo)
end
end
###ConcreteClass
class EarnestWorker < Worker
# 抽象メソッドの実装. コンスタントに仕事をします
def do_task
@output += 1
"Working."
end
# デフォルト実装とは異なる挙動にしたいのでオーバーライド
def lunch
@output += 1
"Lunch and work!"
end
end
class LazyWorker < Worker
# 抽象メソッドの実装. ときたま寝てしまいます
def do_task
if [true, false].sample
@output += 1
"Working."
else
"Take a nap..."
end
end
end
###呼び出し
workers = [LazyWorker.new, EarnestWorker.new]
workers.each do |w|
w.work
end
####実行結果
09:00 Working.
10:00 Take a nap...
11:00 Working.
12:00 Lunch with colleagues.
13:00 Working.
14:00 Working.
15:00 Working.
16:00 Take a nap...
17:00 Working.
Hi boss! I'm LazyWorker.
Today I have finished 6 tasks!
09:00 Working.
10:00 Working.
11:00 Working.
12:00 Lunch and work!
13:00 Working.
14:00 Working.
15:00 Working.
16:00 Working.
17:00 Working.
Hi boss! I'm EarnestWorker.
Today I have finished 9 tasks!
参考
Olsen, R. 2007. Design Patterns in Ruby