概要
「Rubyによるデザインパターン※」を読んでデザインパターンを勉強中。
Javaをやっていた人間としての目線で情報を整理してみます。
※残念ながら日本語版は絶版らしいですが原著はKindleで購入可能です。結構文章も読みやすいので極端な英語アレルギーがある人でなければオススメです。
####他のデザインパターン
Template Method Pattern
Strategy Pattern
- Template Method Pattern と同様に「変わるもの」と「変わらないもの」を分離する
- 継承によりクラスの親子関係として分離するのではなく移譲を用いる
- 具体的な処理は Context クラスから Strategy インターフェースを実装したクラスに移譲される
- 同じインターフェースを実装してさえいれば処理内容の切り替えを用意に行う事が可能
Java での実装
###Context
public class Context {
private String userName;
private GreetingStrategy greetingStrategy;
public void setGreetingStrategy(GreetingStrategy strategy) {
this.greetingStrategy = strategy;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserName() {
return userName;
}
// ロジックがどのような文脈(Context)で呼び出されたのかを知らせるために this を渡しています
public void greet() {
greetingStrategy.greet(this);
}
}
具体的なロジックは greetingStrategy
で参照されるクラスに実装されているので、クラスを入れ替えることでロジックをごっそり変更することができます。また、ConcreteStrategy のロジックの変更、もしくは追加が行われてもこのクラスには影響がありません。
###Strategy
public interface GreetingStrategy {
public void greet(Context ctx);
}
###ConcreteStrategy
具体的なロジックはここで定義。必要な情報は Context オブジェクトから取得します。
public class InEnglishStrategy implements GreetingStrategy {
public void greet(Context ctx) {
System.out.println("Hello! " + ctx.getUserName() + "!");
}
}
public class InJapaneseStrategy implements GreetingStrategy {
public void greet(Context ctx) {
System.out.println("こんにちは!" + ctx.getUserName() + "さん!");
}
}
###呼び出し
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
System.out.print("Enter your name: ");
String name = new Scanner(System.in).nextLine();
Context ctx = new Context();
ctx.setUserName(name);
// 名前にマルチバイト文字が含まれていれば日本語で、
// そうでなければ英語で挨拶するように Strategy を切り替えます
if (str.length() == str.getBytes().length) {
ctx.setGreetingStrategy(new InEnglishStrategy());
} else {
ctx.setGreetingStrategy(new InJapaneseStrategy());
}
ctx.greet();
}
}
実行結果
$> java Main
Enter your name: John Doe
Hello! John Doe!
$> java Main
Enter your name: 名無しの権兵衛
こんにちは!名無しの権兵衛さん!
この例ではロジックがあまりにも単純なので Strategy Pattern を使うメリットはあまり無いですが、ロジックを動的に変更可能である、というのが大きなメリットです。
Ruby での実装
###Context
class Context
attr_accessor :user_name
attr_writer :greeting_strategy
def greet
@greeting_strategy.greet(self)
end
end
###Strategy
不要。
ここが Java エンジニアにはなんとも気持ちの悪いところ。
Ruby においては「同じシグネチャのメソッドを持ったオブジェクトは同じインターフェースを実装している」と考えるべき。
###ConcreteStrategy
class InEnglishStrategy
def greet(ctx)
puts "Hello! #{ctx.user_name}!"
end
end
class InJapaneseStrategy
def greet(ctx)
puts "こんにちは! #{ctx.user_name} さん!"
end
end
###呼び出し
require_relative 'context'
require_relative 'in_english_strategy'
require_relative 'in_japanese_strategy'
print "Enter your name: "
name = gets.chomp
ctx = Context.new
ctx.user_name = name
if name.length == name.bytesize
ctx.greeting_strategy = InEnglishStrategy.new
else
ctx.greeting_strategy = InJapaneseStrategy.new
end
ctx.greet
###実行結果
$> ruby main.rb
Enter your name: John Doe
Hello! John Doe!
$> ruby main.rb
Enter your name: 名無しの権兵衛
こんにちは! 名無しの権兵衛 さん!
Ruby での実装(よりRubyらしく)
この例のようにロジックがシンプルな場合、わざわざ ConcreteStrategy をクラスとして定義するまでもなく、次のようにブロックを渡すようにした方がシンプルでわかりやすく記述できる。
class Context
attr_accessor :user_name
def greet
yield @user_name
end
end
print "Enter your name: "
name = gets.chomp
ctx = Context.new
ctx.user_name = name
if name.length == name.bytesize
ctx.greet {|name| puts "Hello! #{name}!" }
else
ctx.greet {|name| puts "こんにちは! #{name} さん!" }
end
参考
Olsen, R. 2007. Design Patterns in Ruby