1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

「HeadFirstデザインパターン」と「Rubyによるデザインパターン」を読んで Strategy パターン

Last updated at Posted at 2018-07-15

何番煎じか判りませんがお勉強メモを残します

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
# => "翼で飛ぶ"

今回の例では各鴨サブクラスがストラテジの定数を参照している
各鴨サブクラスが各ストラテジの名前を把握していなければならなかったのか?
もしかして、他に選択肢はあったんじゃないのか?

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?