戦略パターン はいつ使用されますか?
次のようなクライアントコードスニペットが表示されます。
class StrategyExample {
public static void main(String[] args) {
Context context;
// Three contexts following different strategies
context = new Context(new ConcreteStrategyAdd());
int resultA = context.executeStrategy(3,4);
context = new Context(new ConcreteStrategySubtract());
int resultB = context.executeStrategy(3,4);
context = new Context(new ConcreteStrategyMultiply());
int resultC = context.executeStrategy(3,4);
}
}
そして、これにリファクタリングできるようです。
class StrategyExample {
public static void main(String[] args) {
// Three contexts following different strategies
int resultA =new ConcreteStrategyAdd().execute(3,4);
int resultB =new ConcreteStrategySubtract().execute(3,4);
int resultC =new ConcreteStrategyMultiply().execute(3,4);
}
}
コードの最初のセクションは、ウィキペディアのページから直接取得されました。大きな違いの1つは、コンテキストがなくなることですが、この例ではとにかく何もしていませんでした。たぶん誰かが戦略が理にかなっているより良い例を持っています。私は通常デザインパターンが好きですが、これは有用性を追加せずに複雑さを追加するようです。
このようなおもちゃの例の問題は、ポイントを見逃しやすいことです。この場合、コードは実際にあなたが示したように実装することができます。戦略パターンの主な価値は、さまざまな状況に応じてさまざまな実装を切り替えることができることです。
あなたが持っている例は、パターン内のオブジェクトとそれらの間の相互作用を示しているだけです。代わりに、実際のWebブラウザーであるかスマートフォンであるかに応じて、Webサイトのグラフをレンダリングするコンポーネントがあり、作成するブラウザーのタイプを検出して別のブラウザーで戦略を設定するコードがあるとします。複製する必要がなく、グラフの実際の描画の詳細を適切な戦略オブジェクトに残して両方の状況で作業を行う複雑なコードで戦略オブジェクトを使用できるコンポーネント:
_interface GraphStrategy {
Image renderGraph(Data graphData);
}
class BigGraphStratedy implements GraphStrategy {
...
}
class SmallGraphStrategy implements GraphStrategy {
...
}
_
次に、他のコードで:
_GraphStrategy graphStrategy;
if (phoneBrowser == true) {
graphStrategy = new SmallGraphStrategy();
} else {
graphStrategy = new BigGraphStrategy();
}
_
残りのアプリケーションコードは、フルイメージレンダリングとスモールイメージレンダリングのどちらが実行されているかを知らなくても、graphStrategy.renderGraph()
を使用できます。
頭に浮かぶ分野:
テーマは、何かを行うか別の方法で行うかの決定は状況要因に依存し、あなたまたはあなたのコードは状況に基づいて正しい戦略を選択するということです。
では、なぜこれが次のようなものよりも役立つのでしょうか。
void DoIt()
{
if (... situation1...)
{
DoA()
}
else
{
DoB();
}
}
その理由は、一度だけ決断を下し、それを忘れたい場合があるからです。戦略パターンのもう1つの重要なテーマは、使用する戦略に関する決定を、戦略を実行する必要のあるコードから切り離すことです。
DoItStrategy MakeDoItStrategy()
{
if (... situation1...)
{
return new DoItStrategyA();
}
else
{
return new DoItStrategyB();
}
}
最後の例では、ストラテジーを保存して、ストラテジーインターフェイスを実装する別のオブジェクトとして渡すことができます。戦略を実行する人にとって、彼らは単に行動を実行する方法を持っています。彼らは内部の仕組みが内部で何であるかを知りません、ただインターフェースが満たされるということだけです。戦略のユーザーは、なぜ私たちが決定を下したのかを知る必要はありません。彼らはただ行動を起こす必要があります。私たちは一度決定を下し、その戦略を使用するクラスにその戦略を渡しました。
たとえば、特定のネットワーク構成に基づいて、UDPを使用してリモートホストに接続してデータを送信するというプログラム全体の決定を行う場合を考えてみます。ネットワークインターフェイスの各ユーザーが決定を下すためのロジック(上記の「DoIt」機能)を知る必要がある代わりに、UDP戦略を事前に作成し、ネットワークデータを送信する必要があるすべての人に渡すことができます。次に、この戦略は、同じ最終結果を持つ単純なインターフェイスを実装します。データはAからBに取得されます。
引用しているコードスニペットは、(わずかに)コンテキストから外れているという点で少し欺瞞的です。以下の例で書いているのは、戦略パターンでもあります。上記の例をもう少し簡潔に書き直しただけです。
この例の要点は、数学演算の詳細が呼び出し元から抽象化されていることです。このようにして、呼び出し元は、新しいConcreteStrategyを作成することにより、任意の2項演算子を操作できます。
int mod = new ConcreteStrategy(){
public int execute(int a, int b){ return a %b; }
}.execute(3,4);
ええ、例は不完全ですが、さまざまな実装戦略を持つデリゲートの概念は、私が多くのアプリで使用してきた基本的な非常に古いデザインパターンです。
ここでのあなたの問題はよくある問題だと思いますが、私が今まで見たほとんどすべてのデザインパターンの例は信じられないほど工夫されており、いつもあなたと同じように質問するようになります。このように提示すると、常に役に立たないように見えます。
MacとiPhoneで使用されるCocoaフレームワークは、戦略パターンa lotを使用します。ただし、これをデリゲートパターンと呼びます。使用方法は次のとおりです。
NSTableView
などの具体的なオブジェクトがあります。テーブルビューは、行数、各行の内容、各列などを知る必要があります。したがって、テーブルビューをサブクラス化してその情報を提供する代わりに、「デリゲート」オブジェクトを提供します。そのデリゲートオブジェクトは、特定のインターフェイス(Objective-Cの「プロトコル」)を実装します。次に、テーブルビューはデリゲートオブジェクトに特定の状況で何をすべきかを尋ねることができます(「行はいくつありますか?」「このセルには何が入りますか?」「ユーザーはこの行を選択できますか?」)。 NSTableViewDelegate
プロトコルに準拠する新しいオブジェクトを割り当てるだけで、実行時にデリゲートオブジェクトを交換できます。
そうですね、戦略パターンは私のお気に入りの1つであり、毎日使用しています。
戦略パターンは、ユーザー(またはコードのユーザー)がアルゴリズムの計算を変更したい場合に役立ちます。戦略パターンを使用した簡単な例は、A *検索でヒューリスティックをモデル化することです。 A *はヒューリスティックを使用します。これは、ゴールノード(Ng)へのパスで特定のノード(Ni)が選択された場合に、残りのコストを推定するための単純な計算です。私のインターフェースは次のようになりました。
class Heuristic {
public:
virtual int estimateRemainingCost(const node &Ni, const node &Ng) const = 0;
};
そして、それらは次のように使用されます。
...
// for each node (Ni) that is adjacent to the current node Nc
int node_priority = cost(Ni)/* the cost of choosing this node on the path */
+ heuristic->estimateRemainingCost(Ni, Ng);
unsearched_nodes_.queue(node_priority, Ni);
...
ヒューリスティックは、置き換えることができる戦略または計算です。言い換えれば、検索アルゴリズムのヒューリスティックを変更するのは簡単な作業です。
私は通常、状況に基づいて、やるべきことがたくさんあるときに戦略パターンを使用します。本質的に、これは長い一連のif/elseステートメントを数行に変換する方法です。
これを行う1つの方法(Javaで):
_Map<String, Strategy> strategyMap = new HashMap<String, Strategy>();
strategyMap.put("bark", new BarkingStrategy());
strategyMap.put("meow", new MeowingStrategy());
strategyMap.put("moo", new MooingStrategy());
strategyMap.put("giraffeSound", new UnknownStrategy());
_
まず、何らかの形の戦略のレパートリーを構築します。
後で...
_String command = //...some form of input
strategyMap.get(command).execute();
_
このようにして、さまざまな状況を「一般的に」処理できます。
つまり:
_moo
_
MooingStrategy()
を実行します
Context
は、提供する操作を前提として、複雑なことを行う方法を知っています。操作は簡単です(2つの数値を加算する、2つの数値を乗算するなど)。 Context
は、重要な例では、「戦略」として最も適切に除外される(1つだけではなく)任意の数の異なる動作を必要とする場合があります(Context
をサブクラス化しようとするため)サブクラスの組み合わせ爆発を作成します)。
コンテキストオブジェクトがより多くの責任を持ち、戦略の抽象化がこれらの責任を操作のある側面から切り離すと、より意味があります。 1つの例(C#)は、IComparerインターフェイスです。
interface IComparer<T>
{
int Compare(T a, T b);
}
これは、ソートアルゴリズムに渡すことができます。
主な違いは、2番目の例では、戦略がアルゴリズムであるということです(したがってパターンはありません)。最初の例では、アルゴリズムの一部を抽象化/分離しています。
たとえば、Context.executeStrategy()
の実装は次のようになります。
public int executeStrategy(int baseValue, int exponentFactor)
{
return (int) Math.Pow(baseValue, this.Strategy.Calculate(2, exponentFactor));
}