JavaRush /Java Blog /Random-JA /デザインパターン「戦略」

デザインパターン「戦略」

Random-JA グループに公開済み
こんにちは!これまでの講義で、「デザインパターン」という概念についてはすでに説明しました。忘れた場合のために思い出してください。この用語は、プログラミングにおける一般的な問題に対する特定の標準的な解決策を指します。 デザインパターン「戦略」 - 1JavaRush では、ほとんどすべての質問に対する答えは Google で検索できるとよく言います。したがって、おそらく誰かがあなたと同様の問題をすでに解決しているでしょう。したがって、パターンとは、最も一般的な問題に対する長年の実績と実践で実証された解決策、または問題状況を解決するための方法です。これらはまさに「自転車」であり、いかなる場合でも自分で発明する必要はありませんが、いつどのように適用するかを知る必要があります:) パターンのもう 1 つのタスクは、アーキテクチャを単一の標準に準拠させることです。他人のコードを読むのは簡単な作業ではありません。同じ問題をさまざまな方法で解決できるため、人によって書き方が異なります。しかし、パターンを使用すると、さまざまなプログラマーがコードのすべての行を詳しく調べることなく (たとえ初めて見た場合でも) プログラムのロジックを理解できるようになります。今日は、「戦略」と呼ばれる最も一般的なパターンの 1 つを見ていきます。 デザインパターン「戦略」 - 2Car オブジェクトを積極的に操作するプログラムを作成していると想像してみましょう。この場合、プログラムが正確に何をするかは特に重要ではありません。Autoこれを行うために、1 つの親クラスと 3 つの子クラス (および )を含む継承システムを作成しました SedanTruckF1Car
public class Auto {

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {

       System.out.println("Тормозим!");
   }
}

public class Sedan extends Auto {
}

public class Truck extends Auto {
}

public class F1Car extends Auto {
}
3 つの子クラスはすべて、親から 2 つの標準メソッドを継承します。gas()また、stop() プログラムは非常に単純です。車は前進とブレーキのみを実行できます。作業を続けながら、私たちは車に新しい方法fill()(燃料補給) を追加することにしました。それを親クラスに追加しましょうAuto
public class Auto {

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {

       System.out.println("Тормозим!");
   }

   public void fill() {
       System.out.println("Заправить бензин!");
   }
}
このような単純な状況でも問題が発生する可能性があるように思えますか?まあ、実際には、それらはすでに発生しています... デザインパターン「戦略」 - 3
public class ChildrenBuggies extends Auto {

   public void fill() {

       //хм... Это детский багги, его не надо заправлять :/
   }
}
私たちのプログラムには、一般的な概念に当てはまらない車、つまり子供用バギーが登場しました。ペダル式だったりラジコンだったりするかもしれないが、一つ確かなことは、ガソリンを入れる場所がないということだ。私たちの継承スキームにより、共通メソッドを必要としないクラスにも共通メソッドを与えることになりました。このような状況ではどうすればよいでしょうか? たとえば、fill()クラス内のメソッドをオーバーライドしてChildrenBuggies、バギーに燃料を補給しようとしても何も起こらないようにできます。
public class ChildrenBuggies extends Auto {

   @Override
   public void fill() {
       System.out.println("Игрушечную машину нельзя заправить!");
   }
}
しかし、少なくともコードの重複があるため、この解決策は成功したとは言い難いです。たとえば、ほとんどのクラスは親クラスのメソッドを使用しますが、他のクラスはそれをオーバーライドすることを強制されます。15 個のクラスがあり、5 ~ 6 個で動作をオーバーライドする必要がある場合、コードの重複はかなり広範囲になります。インターフェースが役に立つかも?たとえば、これは次のとおりです。
public interface Fillable {

   public void fill();
}
Fillable1 つのメソッドを使用して インターフェイスを作成しますfill()。したがって、燃料を補給する必要がある車はこのインターフェイスを実装しますが、他の車 (たとえば、バギー) は実装しません。しかし、このオプションも私たちには適していません。私たちのクラス階層は、将来的には非常に多くなるかもしれません (世界中にどれだけの種類の車があるか想像してみてください)。をオーバーライドしたくなかったため、以前の継承オプションを放棄しましたfill()。ここでは、すべてのクラスに実装する必要があります。50 個ある場合はどうなるでしょうか? そして、プログラムに頻繁に変更が加えられた場合 (実際のプログラムでは、ほとんどの場合そうなります!)、50 クラスすべての間で舌を這わせて走り回り、各クラスの動作を手動で変更する必要があります。では、結局何をすればいいのでしょうか?問題を解決するには、別の道を選択しましょう。つまり、クラスの動作をクラス自体から分離しましょう。それはどういう意味ですか?ご存知のとおり、オブジェクトには状態 (データのセット) と動作 (メソッドのセット) があります。マシン クラスの動作は、 と の 3 つのメソッドで構成さgas()stop()ますfill()。最初の 2 つの方法は問題ありません。ただし、3 番目のメソッドをクラスの外に移動しますAuto。これは、クラスから動作を分離することになります (より正確には、動作の一部のみを分離します。最初の 2 つのメソッドはそのまま残ります)。メソッドをどこに移動する必要がありますかfill()? すぐには何も思い浮かびません :/ 彼は完全にその場にいるようでした。それを別のインターフェースに移動します - FillStrategy!
public interface FillStrategy {

   public void fill();
}
なぜこのインターフェースが必要なのでしょうか? それは簡単です。これで、このインターフェースを実装するいくつかのクラスを作成できます。
public class HybridFillStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Заправляем бензином or электричеством на выбор!");
   }
}

public class F1PitstopStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Заправляем бензин только после всех остальных procedures пит-стопа!");
   }
}

public class StandartFillStrategy implements FillStrategy {
   @Override
   public void fill() {
       System.out.println("Просто заправляем бензин!");
   }
}
私たちは、従来型車、ハイブリッド車、F1 カー向けの 3 つの動作戦略を作成しました。各戦略は個別の給油アルゴリズムを実装します。この例では、これはコンソールに出力されるだけですが、メソッド内に複雑なロジックが存在する可能性があります。次はこれをどうすればいいでしょうか?
public class Auto {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }

}
インターフェースをFillStrategy親クラスのフィールドとして使用しますAuto。注: 特定の実装を指定するのではなく、インターフェイスを使用します。FillStrategyそして、子カークラスに インターフェイスの特定の実装が必要になります。
public class F1Car extends Auto {

   public F1Car() {
       this.fillStrategy = new F1PitstopStrategy();
   }
}

public class HybridAuto extends Auto {

   public HybridAuto() {
       this.fillStrategy = new HybridFillStrategy();
   }
}

public class Sedan extends Auto {

   public Sedan() {
       this.fillStrategy = new StandartFillStrategy();
   }
}
何が得られたか見てみましょう:
public class Main {

   public static void main(String[] args) {

       Auto sedan = new Sedan();
       Auto hybrid = new HybridAuto();
       Auto f1car = new F1Car();

       sedan.fill();
       hybrid.fill();
       f1car.fill();
   }
}
コンソール出力:

Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
素晴らしい、給油プロセスは正常に機能しています。ちなみに、ストラテジをコンストラクターのパラメーターとして使用することを妨げるものはありません。たとえば、次のようになります。
public class Auto {

   private FillStrategy fillStrategy;

   public Auto(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }

   public void fill() {
       this.fillStrategy.fill();
   }

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }
}

public class Sedan extends Auto {

   public Sedan() {
       super(new StandartFillStrategy());
   }
}



public class HybridAuto extends Auto {

   public HybridAuto() {
       super(new HybridFillStrategy());
   }
}

public class F1Car extends Auto {

   public F1Car() {
       super(new F1PitstopStrategy());
   }
}
メソッドを実行してmain()(変更せずに)、同じ結果を得てみましょう。コンソール出力:

Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
Strategy パターンは、アルゴリズムのファミリーを定義し、それぞれをカプセル化し、それらが交換可能であることを保証します。これにより、クライアント側での使用に関係なく、アルゴリズムを変更できます (この定義は書籍「Exploring Design Patterns」から引用したもので、私には非常に成功しているように思えます)。 デザインパターン「戦略」 - 4私たちは、関心のあるアルゴリズムのファミリー (給油車のタイプ) を、いくつかの実装を備えた個別のインターフェイスに分離しました。私たちはそれらを車の本質から切り離しました。したがって、現在、給油プロセスに何らかの変更を加える必要がある場合でも、これは当社のクラスの自動車には一切影響しません。互換性に関しては、これを実現するには、クラスに setter メソッドを 1 つ追加するだけですAuto
public class Auto {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }

   public void setFillStrategy(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }
}
これで、その場で戦略を変更できるようになりました。
public class Main {

   public static void main(String[] args) {

       ChildrenBuggies buggies = new ChildrenBuggies();
       buggies.setFillStrategy(new StandartFillStrategy());

       buggies.fill();
   }
}
突然、子供用のバギー車にガソリンが充填され始めた場合、私たちのプログラムはそのようなシナリオに対応する準備ができています:) 実際にはそれだけです。もう 1 つのデザイン パターンを学習しました。これは間違いなく必要になり、実際のプロジェクトに取り組むときに何度も役立ちます :) また会いましょう!
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION