メインコンテンツへ移動

デザインパターン:ストラテジーパターン(Strategy Pattern)

無料2015-03-06#Design_Pattern#策略模式#Strategy Pattern

デザインパターンはコード構造の最適化経験から抽出された理論的知識であり、成熟したデザインパターンを適用することでコードの再利用性、拡張性、保守性を向上させることができます。その中で、ストラテジーパターンは最も基本的なデザインパターンの一つであり、簡単に言えば、置換可能なアルゴリズムステップをアルゴリズムファミリーとしてカプセル化し、実行時に動的に選択できるようにするものです。

no_mkd

一.ストラテジーパターン(Strategy Pattern)とは?

文字通り理解すると、ストラテジーパターンとは何らかの「ストラテジー」を適用したデザインパターンであり、この「ストラテジー」とは:変化する部分をカプセル化することです。

実はこの理解には誤りがあり、例に大きな間違いはありませんが、このパターンに対する理解に偏差がありました。修正内容は本文末尾に追加しています。こちらをクリックしてジャンプ>>

二.例を挙げる

仮に今、Dog を表すクラスを作成する必要があるとしましょう。まず、すべての Dog には外見(例えば Color)があり、行動(例えば Run)があります。そこで、私たちは自然にこのような Dog ベースクラスを定義します:

public abstract class Dog {
	public abstract void display();//显示 Dog 的外形
	public abstract void run();//定义 Dog 的 Run 行为
}

他の Dog 具体クラスはすべて Dog ベースクラスを継承すればよいのですが、すぐに新しい要件が来ました:Red Dog がいます。これはペット犬で、ゆっくり走り、鳴き声も小さいです。そこで RedDog クラスを定義しました:

public class RedDog {
	 @override
	public void display(){
		System.out.println("this is a red dog.");
	}
	 @override
	public void run(){
		System.out.println("it's running slowly.");
	}
}

次に、大量の要件が来ました。Black Dog、Gray Dog、Pink Dog、BlackGrayDog、PinkGrayDog。。。合計 100 匹の Dog で、Color が異なるだけでなく、Run の速度も異なります。そこで、100 種類の異なるタイプの Dog を表すために 100 個のクラスを定義し、各クラスで Run メソッドと Display メソッドを実装しました。

嗯~、クラス図を見てみましょう。そうです、この BigBang が私たちのクラス図です。とても合理的に思えます。100 種類の Dog があれば 100 個のクラスがあるのは当然で、具体的な Dog クラスは Dog ベースクラスを継承するべきでしょう?嗯、そのようです。そして、これには何も問題がないように思えます。少なくとも現時点では何も不適切な点は見つかりません。。。

また新しい要件が来ました。Dog は Run だけでなく Bark もできることがわかりました。自然に、Dog ベースクラスに新しい行動 Bark を定義し、100 個の具体クラスで Bark メソッドを一つずつオーバーライドしました。。。プロセスは少し面倒でしたが、問題を解決できました。今ではすべての Dog が Bark できます。

ある日、新しい Dog が来ました。ToyDog と呼ばれ、おもちゃの犬で、Run も Bark もできません。ただのおもちゃです。そこで ToyDog に Dog ベースクラスを継承させ、Run メソッドと Bark メソッドをオーバーライドしました(メソッド本体は空です。Toy は Run も Bark もできないため)。問題も完璧に解決されたように見えました。少なくとも現時点では何も問題はないように思えました。

何日後、ToyDog 工場が技術革新し、新製品に電池が搭載されて Bark できるようになりました。問題ありません。ToyDog クラスを修正して Bark を実装しました。ToyDog を初期化すると、楽しそうに BarkBarkBark と鳴きます。しかし、しばらくすると電池が切れて、ToyDog は Bark できなくなりましたが、私たちの ToyDog クラスはまだ Bark できると表示しています。。。

ついに問題が発生しました。Dog 問題で最も扱いにくい部分が Run と Bark のこの 2 つの行動であることがわかりました。変化一旦发生すれば具体的な Dog クラスを修正する必要があり、場合によっては Dog ベースクラスさえ修正する必要があります。大量の時間を費やすだけでなく、すべての具体的な Dog クラスで Run と Bark メソッドを実装しており、非常に冗長です。さらに最も重要な問題は、Dog の行動を動的に修正できないことです。例えば、小さな Dog はゆっくり走り鳴き声も小さいが、成長すると速く走り鳴き声も大きくなることや、上記の電池切れによる行動変化問題にも対応できません。。。。この一連の問題は私たちに一つのことを示しています:これは最初から悪い設計だったということです。

三.ストラテジーパターンをどのように適用するか?

ストラテジーパターンは変化する部分をカプセル化することを要求します。まず、コード内で頻繁に変化する部分を見つける必要があります。前の例では、変化する部分は何でしょうか?

1.Run 行動;2.Bark 行動;3.その他存在し得る行動

次に、これらの行動をカプセル化します(Run を例として):

package StrategyPattern;

/**

  • @author ayqy
  • 定义 Run 接口

*/ public interface IRun { public void run();//定义 run 行为 }

package StrategyPattern;

/**
 * @author ayqy
 * 实现 RunQuickly 行为
 *
 */
public class RunQuickly implements IRun{

	 @Override
	public void run() {
		System.out.println("it's running quicky.");
	}

}
package StrategyPattern;

/**
 * @author ayqy
 * 实现 RunSlowly 行为
 *
 */
public class RunSlowly implements IRun{

	 @Override
	public void run() {
		System.out.println("it's running slowly.");
	}

}
package StrategyPattern;

/**
 * @author ayqy
 * 实现 RunNoWay 行为
 *
 */
public class RunNoWay implements IRun{

	 @Override
	public void run() {
		System.out.println("it isn't able to run.");
	}

}

Bark 行動のカプセル化もこれに似ています。「変化」をカプセル化した後、Dog ベースクラスもそれに応じて変更する必要があります:

package StrategyPattern;

/**

  • @author ayqy
  • 定义 Dog 基类

*/ public abstract class Dog { IBark bark; IRun run;

public abstract void display();//显示 Dog 的外形

public IBark getBark() {
	return bark;
}

public void setBark(IBark bark) {
	this.bark = bark;
}

public IRun getRun() {
	return run;
}

public void setRun(IRun run) {
	this.run = run;
}

}

注意してください。変化しやすい部分(Bark と Run)のみをカプセル化し、Display メソッドはカプセル化していません(Dog の外見は比較的 fixed でしょう)。「変化する部分」とは何でしょうか?これは慎重に考慮する必要があり、具体的な状況によります。

では、新しい具体的な Dog クラスを見てみましょう。

package StrategyPattern;

/**

  • @author ayqy
  • 实现 RedDog

*/ public class RedDog extends Dog{

public RedDog()
{
	//红狗是宠物狗
	//跑得慢,叫声小
	super.setRun(new RunSlowly());
	super.setBark(new BarkQuietly());
}

 @Override
public void display() {
	System.out.println("this is a red dog.");
}

}

クラスが明らかにスリムになりました。しかも、実行時に Dog の行動を動的に修正できるようになりました。ストラテジーパターンの威力をご覧ください。

四.効果の例

テストクラスを作成します:

package StrategyPattern;

/**

  • @author ayqy
  • 测试策略模式<br/>
  • 策略模式,简单的说就是要求把“变化”封装起来,以隔离“变化”对其它部分的影响

*/ public class Test {

/**
 * @param args
 */
public static void main(String[] args) {
	System.out.println("------- 创造一只 Red Dog -------");
	Dog redDog = new RedDog();
	showDogInfo(redDog);
	
	System.out.println("\n------- 创造一只 Black Dog -------");
	Dog blackDog = new BlackDog();
	showDogInfo(blackDog);
	
	System.out.println("\n------- 创造一只 Toy Dog -------");
	Dog toyDog = new ToyDog();
	showDogInfo(toyDog);
	
	System.out.println("\n------- 技术革新,现在 Toy Dog 可以小声叫了 -------");
	toyDog.setBark(new BarkQuietly());
	showDogInfo(toyDog);
	
	//上面的代码并不是最优的,只是为了说明策略模式
	//一个很明显的问题是 Dog redDog = new RedDog();
	//我们在面向具体对象编码,而不是设计模式所提倡的面向接口编码
	//可以定义一个 Dog 工厂来生产 Dog 对象以求模块之间更松的耦合
}

/**
 * @param dog
 * 显示 Dog 相关信息
 */
private static void showDogInfo(Dog dog)
{
	dog.display();
	dog.run.run();
	dog.bark.bark();
}

}

実行結果の例:

五.まとめ

ストラテジーパターンの核心は、頻繁に変化する部分をカプセル化することであり、その役割は変化部分の影響を分離し、局所的な変化が他の fixed 部分に影響を与えることを防ぐことです。設計にはより多くの時間がかかるかもしれませんが、保守、再利用、拡張が容易になります。この例では、Run、Bark 行動は新しいクラス(例えば Pig)でも再利用できます。行動が変化した場合、各行動インターフェースを修正するだけで済み、最大でも Dog ベースクラスを簡単に修正するだけで変化に対応できます。

六.修正

上記の内容にはいくつかの偏差がありますが、何とか見られるレベルです。ただし、あまり正確ではありません。以下に正確な説明をします:

ストラテジーパターンの思想は確かにカプセル化ですが、ここでの「ストラテジー」は「変化をカプセル化する」ことを指すわけではありません。これは原文を繰り返し読んだ後に発見しました(『Head First デザインパターン』第 1 章は少し理解しにくく、最初の理解が偏っていました)。

ここでの「ストラテジー」はアルゴリズムに近似します。

ストラテジーパターンは理解しにくいですが(筆者は原文をそのままコピーすることを極力避けましたが、万一また理解が偏って他人を誤解させることを恐れています。。)、書籍の正確な定義は以下の通りです:

ストラテジーパターンはアルゴリズムファミリーを定義し、それぞれをカプセル化して相互に置換可能にします。このパターンにより、アルゴリズムの変化をアルゴリズムを使用するクライアントから独立させることができます。

個人で繰り返し読んだ後、原文が表現したいことは以下の通りだと考えます:

  • 1.ストラテジーパターンの核心はアルゴリズムのカプセル化です(「テンプレートメソッドパターン」と呼ばれる別のデザインパターンもあり、これもアルゴリズムのカプセル化です。違いと連絡は中で紹介します。こちらをクリックしてジャンプ>>
  • 2.アルゴリズム(ストラテジー)の選択の実装に注力し、実行時にストラテジーを動的に変更することをサポートします
  • 3.具体的な実装は、変化する部分を見つけてインターフェースとして定義することです。各インターフェースは一組のアルゴリズムに対応し、それぞれが一つのストラテジーです
  • 4.同じインターフェース下のアルゴリズムは相互に置換可能です
  • 5.アルゴリズムはクライアントコードから独立しており、これはアルゴリズムカプセル化の具体的な現れです

コメント

コメントはまだありません

コメントを書く