デザインパターン_行動パターン「戦略パターン」
ダークホースプログラマによるJavaデザインパターンの詳細解説、23のJavaデザインパターン(図表+フレームワークソースコード解析+実戦)をノートとしてまとめています。
概要
まず下の写真を見てください。自転車、車、電車、飛行機など、私たちの移動手段はたくさんあります。
プログラマは、開発のための開発ツールを選択する必要があります。もちろん、コード開発のためのツールはたくさんあります。開発のためのアイデアを選択することも、開発に Eclipse を使用することも、その他の開発ツールを使用することもできます。
意味
- このモードでは、一連のアルゴリズムを定義し、各アルゴリズムを相互に置き換えられるようにカプセル化します。アルゴリズムの変更は、アルゴリズムを使用する顧客に影響を与えません。戦略パターンはオブジェクトの動作パターンに属し、アルゴリズムをカプセル化し、アルゴリズムの使用責任をアルゴリズムの実現から分離し、これらのアルゴリズムの管理をさまざまなオブジェクトに委任します。
構造
戦略パターンの主な役割は次のとおりです。
- 抽象戦略 (Strategy) クラス: これは抽象的な役割であり、通常はインターフェイスまたは抽象クラスによって実装されます。この役割は、具体的な戦略クラスに必要なすべてのインターフェイスを提供します。
- 具体的な戦略 (具体的な戦略) クラス: 抽象的な戦略によって定義されたインターフェイスを実装し、特定のアルゴリズムの実装または動作を提供します。
- 環境 (コンテキスト) クラス: 最終的にクライアントによって呼び出される戦略クラスへの参照を保持します。
事例の実現
【例】プロモーション
デパートでは毎年恒例のセールプロモーションを行っています。さまざまなフェスティバル(春節、中秋節、クリスマス)に応じてさまざまなプロモーション活動を開始し、プロモーターは顧客にプロモーション活動を示します。クラス図は次のとおりです。
コードは以下のように表示されます:
-
デパート内のすべてのプロモーションに共通のインターフェースを定義する
// 抽象策略类 public interface Strategy { void show(); }
-
具体的な戦略的役割の定義(具体的戦略):各フェスティバルの具体的なプロモーション活動
// 具体策略类-为春节准备的促销活动A public class StrategyA implements Strategy { public void show() { System.out.println("买一送一"); } } // 具体策略类-为中秋节准备的促销活动B public class StrategyB implements Strategy { public void show() { System.out.println("满200元减50元"); } } // 具体策略类-为圣诞节准备的促销活动C public class StrategyC implements Strategy { public void show() { System.out.println("满1000元加一元换购任意200元以下商品"); } }
-
環境の役割を定義します (コンテキスト): コンテキストを接続するために使用されます。つまり、プロモーション活動を顧客に販売するために使用されます。ここでは、販売員として理解できます。
// 环境类-销售员 public class SalesMan { // 聚合策略类 持有抽象策略角色的引用 private Strategy strategy; public SalesMan(Strategy strategy) { this.strategy = strategy; } // 向客户展示促销活动 public void salesManShow(){ strategy.show(); } }
-
テストクラス
public class Client { public static void main(String[] args) { // 春节来了,使用春节促销活动 SalesMan salesMan = new SalesMan(new StrategyA()); // 展示促销活动 salesMan.salesManShow(); System.out.println("=============="); // 中秋节到了,使用中秋节的促销活动 salesMan.setStrategy(new StrategyB()); // 展示促销活动 salesMan.salesManShow(); System.out.println("=============="); // 圣诞节到了,使用圣诞节的促销活动 salesMan.setStrategy(new StrategyC()); // 展示促销活动 salesMan.salesManShow(); } }
出力
买一送一 ============== 满200元减50元 ============== 满1000元加一元换购任意200元以下商品
長所と短所
アドバンテージ
- 戦略クラスを自由に切り替える
- ストラテジークラスはすべて同じインターフェースを実装しているため、自由に切り替えることができます。
- 拡張しやすい
- 新しいストラテジーを追加するには、特定のストラテジークラスを追加するだけでよく、「開閉原則」に準拠した元のコードを基本的に変更する必要はありません。
- 複数の条件選択ステートメント (if else) の使用を避け、オブジェクト指向の設計思想を完全に具体化します。
欠点がある
- すべてのポリシー クラスを外部に公開する必要があり、クライアントはすべてのポリシー クラスを知っていて、どれを使用するかを決定する必要があります。
- ストラテジ パターンは多くのストラテジ クラスを生成しますが、フライウェイト パターンを使用することでオブジェクトの数をある程度減らすことができます。
使用するシーン
- システムが複数のアルゴリズムのうち 1 つを動的に選択する必要がある場合、各アルゴリズムを戦略クラスにカプセル化できます。
- クラスは複数の動作を定義しており、これらの動作は、このクラスの操作で複数の条件ステートメントの形式で表示されます。これらの条件ステートメントの代わりに、各条件分岐を独自のストラテジ クラスに移動できます。
- システム内の各アルゴリズムは互いに完全に独立しており、特定のアルゴリズムの実装の詳細をクライアントから隠す必要があります。
- アルゴリズムを使用するクライアントが、そのアルゴリズムが操作するデータを知らないようにシステムが要求する場合、戦略パターンを使用して、アルゴリズムに関連付けられたデータ構造を隠すことができます。
- 複数のクラスの唯一の違いは、それらの動作が異なることです。ストラテジ モードを使用すると、実行時に実行される特定の動作を動的に選択できます。
JDKソースコード解析 - コンパレーター
Comparator
の戦略パターン。Arrays クラスには次のようなメソッドがありますsort()
。
public class Arrays{
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
}
Arrays は環境ロール クラスであり、sort メソッドは新しい戦略を渡して、この戦略に従って配列を並べ替えることができます。たとえば、次のテストクラスです。
public class demo {
public static void main(String[] args) {
Integer[] data = {
12, 2, 3, 2, 4, 5, 1};
// 实现降序排序
Arrays.sort(data, new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
System.out.println(Arrays.toString(data)); // [12, 5, 4, 3, 2, 2, 1]
}
}
ここで、Arrays の sort メソッドを呼び出すとき、2 番目のパラメータは Comparator インターフェイスのサブ実装クラス オブジェクトです。したがって、Comparator は抽象的な戦略ロールとして機能し、特定のサブ実装クラスは具体的な戦略ロールとして機能します。アンビエント ロール クラス (配列) は、呼び出す抽象ポリシーへの参照を保持する必要があります。では、Arrays クラスの sort メソッドは Comparator サブ実装クラスのメソッドを使用するのでしょうかcompare()
? sort()
引き続きTimSort クラスのメソッドを見てみましょう。コードは次のとおりです。
class TimSort<T> {
static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
T[] work, int workBase, int workLen) {
assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;
int nRemaining = hi - lo;
if (nRemaining < 2)
return; // Arrays of size 0 and 1 are always sorted
// If array is small, do a "mini-TimSort" with no merges
if (nRemaining < MIN_MERGE) {
int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
binarySort(a, lo, hi, lo + initRunLen, c);
return;
}
...
}
private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,Comparator<? super T> c) {
assert lo < hi;
int runHi = lo + 1;
if (runHi == hi)
return 1;
// Find end of run, and reverse range if descending
if (c.compare(a[runHi++], a[lo]) < 0) {
// Descending
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
runHi++;
reverseRange(a, lo, runHi);
} else {
// Ascending
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
runHi++;
}
return runHi - lo;
}
}
上記のコードは最終的にcountRunAndMakeAscending()
このメソッドに実行されます。このメソッドでメソッドが呼び出されていることがわかりますc.compare()
。したがって、Arrays.sort メソッドを呼び出すと、特定の比較オーバーライド メソッドのクラス オブジェクトのみが渡されます。これは、Comparator インターフェイスのサブクラスによって実装する必要があるメソッドでもあります。これが戦略パターンの具体化である。