何番煎じか判りませんがお勉強メモを残します
「HeadFirstデザインパターン」第1章
「Rubyによるデザインパターン」第4章
Strategy パターン
「HeadFirstデザインパターン」でのJavaコードは(だいたい)こんな感じ
鴨シミュレータが例として使用されていた
鴨はみんな「ガーガー」と鳴くんですよ
abstract class Duck {
public void quack() {
// ガーガーと鳴く
}
abstract void display(); // サブクラスに実装、対処させる
}
public class MallardDuck extends Duck {
public void display() {
// マガモの表示
}
}
public class RedHeadDuck extends Duck {
public void display() {
// アメリカンホシハジロの表示
}
}
他にもたくさんの鴨クラスがあります
そして、fly(飛ぶ)メソッドを追加する必要が出た、という設定で改修を続けていきます
スーパーなDuckクラスにメソッドを追加するだけですべての鴨が飛べるようになります
さすがオブジェクト指向ですよ
abstract class Duck {
public void quack() {
// ガーガーと鳴く
}
abstract void display(); // サブクラスに実装、対処させる
public fly() {
// 飛ぶ
}
}
これでおk、実行してみると
ラバーダックや模型の鴨のオブジェクトが空を飛んでいる事が発覚します
そういや、そういう特殊な鴨クラスがあったのでした
修正時に忘れていたのですね、すぐに修正します
public class RubberDuck extends Duck {
public void quack() {
// ラバーダックはキューキューと音を出す
}
public void display() {
// ラバーダックの表示
}
public void fly() {
// ラバーダックは飛ばない
}
}
public class DecoyDuck extends Duck {
public void quack() {
// 模型の鴨は鳴かない
}
public void display() {
// 模型の鴨の表示
}
public void fly() {
// 模型の鴨は飛ばない
}
}
どうやら今後、鴨の種類や振る舞いはどんどん増えていくようです
そのつど、fly()やquack()を調べ、必要ならオーバーライドしていかなければなりません
鴨シミュレータプロジェクトが終了するまでずっとです
これは つらい
いまこそ Strategy パターン の出番だ!
// 「飛ぶ」 振る舞いたち
public interface FlyBehavior {
public void fly();
}
public class FlyWithWings implements FlyBehavior {
public void fly() {
// 翼で飛ぶ
}
}
public class FlyNoWay implements FlyBehavior {
public void fly() {
// 飛ばない
}
}
// 「鳴く」 振る舞いたち
public interface QuackBehavior {
public void quack();
}
public class Quack implements QuackBehavior {
public void quack() {
// ガーガーと鳴く
}
}
public class MuteQuack implements QuackBehavior {
public void quack() {
// 鳴かない
}
}
public class Squeak implements QuackBehavior {
public void quack() {
// キューキューと音を出す
}
}
public abstract class Duck {
FlyBehavior flyBehavior; // ストラテジを記憶するインスタンス変数を用意、サブクラスでも使えるようになるわけです
QuackBehavior quackBehavior;
public void quack() {
quackBehavior.quack(); // ストラテジに移譲
}
public void fly() {
flyBehavior.fly();
}
abstract void display(); // サブクラスに実装、対処させる
}
public class MallardDuck extends Duck {
public MallardDuck() {
flyBehavior = new FlyWithWings(); // 翼で飛ぶ
quackBehavior = new Quack(); // ガーガーと鳴く
}
public void display() {
// マガモの表示
}
}
public class RubberDuck extends Duck {
public RubberDuck() {
flyBehavior = new FlyNoWay(); // 飛ばない
quackBehavior = new Squeak(); // キューキューと音を出す
}
public void display() {
// ラバーダックの表示
}
}
public class DecoyDuck extends Duck {
public RubberDuck() {
flyBehavior = new FlyNoWay(); // 飛ばない
quackBehavior = new MuteQuack(); // 鳴かない
}
public void display() {
// 模型の鴨の表示
}
}
コード内の変化する部分と変化しない部分を分離、変化する部分をカプセル化する事ができる
飛ぶ振る舞いが増えても、鳴く振る舞いが増えても、既存部分に何ら影響を及ぼさない
インスタンス変数に振る舞いを保持しているだけだから、後から動的に変えることもできる
Rubyではどうなるんや?
# 「飛ぶ」 振る舞いたち
class FlyWithWings
def fly
p '翼で飛ぶ'
end
end
class FlyNoWay
def fly
p '飛ばない'
end
end
# 「鳴く」 振る舞いたち
class Quack
def quack
p 'ガーガーと鳴く'
end
end
class MuteQuack
def quack
p '鳴かない'
end
end
class Squeak
def quack
p 'キューキューと音を出す'
end
end
class Duck
def quack
@quack_behavior.quack
end
def fly
@fly_behavior.fly
end
end
class MallardDuck < Duck
def initialize
@fly_behavior = FlyWithWings.new # 翼で飛ぶ
@quack_behavior = Quack.new # ガーガーと鳴く
end
def display
p 'マガモの表示'
end
end
class RubberDuck < Duck
def initialize
@fly_behavior = FlyNoWay.new # 飛ばない
@quack_behavior = Squeak.new # キューキューと音を出す
end
def display
p 'ラバーダックの表示'
end
end
class DecoyDuck < Duck
def initialize
@fly_behavior = FlyNoWay.new # 飛ばない
@quack_behavior = MuteQuack.new # 鳴かない
end
def display
p '模型の鴨の表示'
end
end
mallard_duck = MallardDuck.new
mallard_duck.display
# => "マガモの表示"
mallard_duck.fly
# => "翼で飛ぶ"
mallard_duck.quack
# => "ガーガーと鳴く"
Template Method パターンの欠点の殆どは、パターン自体が継承の上に成り立っている、という事
どんなに注意深くコードを設計してもサブクラスはそのスーパークラスに依存してしまう
Strategyパターン は同じ目的を持った一群のオブジェクトを定義することにある
それぞれのストラテジオブジェクトは同じIFを持ち、似たような仕事を行い、関心・責務をクラスから分離する
移譲と集約に基づいているため、実行時にストラテジを切り替えるのも楽
おそらくrubyであれば
- 移譲は forwardable
- 切り替えは attr_writer, attr_accessor
を使用してナウいようになっていくのかもしれない
require 'forwardable'
class Duck
extend Forwardable
attr_accessor :fly_behavior
attr_accessor :quack_behavior
def_delegator :@fly_behavior, :fly
def_delegator :@quack_behavior, :quack
end
class DecoyDuck < Duck
def initialize
@fly_behavior = FlyNoWay.new # 飛ばない
@quack_behavior = MuteQuack.new # 鳴かない
end
def display
p '模型の鴨の表示'
end
end
decoy_duck = DecoyDuck.new
decoy_duck.display
# => "模型の鴨の表示"
decoy_duck.fly
# => "飛ばない"
decoy_duck.fly_behavior = FlyWithWings.new # ストラテジを変えてみる
decoy_duck.fly
# => "翼で飛ぶ"
今回の例では各鴨サブクラスがストラテジの定数を参照している
各鴨サブクラスが各ストラテジの名前を把握していなければならなかったのか?
もしかして、他に選択肢はあったんじゃないのか?