Flutter/Dart

【Flutter/Dart】Strategyパターン

asoacasio

Dart言語でStrategyパターンを説明します。

ある目的を達成するための戦略がいくつかあり、それらをいつでも交換できるように設計するのがStrategyパターンです。

Strategy(ストラテジー)は日本語に翻訳すると、戦略になります。

メリット

  • 柔軟性:状況に応じてアルゴリズムを変えることが簡単にできます。
  • 保守性:各アルゴリズムが独立して存在するため、特定のアルゴリズムの変更は他のアルゴリズムに影響を与える可能性が低いです。また、アルゴリズムのテストもしやすいです。
  • 拡張性:新しいアルゴリズムを増やす際は、インターフェースを継承したクラスを増やすだけになります。

効果的に利用されるシナリオ

ソート(並べ替え)アルゴリズム

データのサイズや特性によって、最適なソートアルゴリズムが異なる場合があります。

例えば、クイックソート、マージソート、バブルソートなどをそれぞれストラテジーとして実装し、実行時の状況に応じて最適なソートストラテジーを選択することができます。

通信プロトコル

通信環境や要件によって、最適な通信プロトコルが異なる場合があります。

例えば、HTTP、FTP、SMTPなどをそれぞれのストラテジーとして実装し、実行時に状況に応じて最適な通信方式に変更することができます。

サンプルコード

Strategyパターンのイメージが付きやすいように、ウサギが川を渡る例でサンプルコードを書いてみます。

ウサギが川を渡らなければいけない問題に直面しました。

川を渡る方法はいくつかあります。

  1. 泳いで渡る:速くて効率的だが、体力が持つか分かりません。
  2. 橋を作って渡る:安全だけど、時間がかかります。
  3. 浅瀬を見つけて渡る:安全で時間もかからないけど、浅瀬が見つかるかは分かりません。

どの戦略が最適かは、体調・時間・天候などに依ります。

状況に応じて最適な戦略を選ぶことがStrategyパターンの考え方です。

abstract class Strategy {
  String execute();
}

// 泳いで渡る戦略
class SwimStrategy implements Strategy {
  @override
  String execute() {
    return 'ウサギは泳ぎました';
  }
}

// 橋を渡る戦略
class BridgeStrategy implements Strategy {
  @override
  String execute() {
    return 'ウサギは橋を渡りました';
  }
}
// 浅瀬を渡る戦略
class FordStrategy implements Strategy {
  @override
  String execute() {
    return 'ウサギは浅瀬を渡りました';
  }
}

class Rabbit {
  final Strategy strategy;

  Rabbit(this.strategy);

  String crossRiver() {
    return strategy.execute();
  }
}

void main() {
  final rabbit1 = Rabbit(SwimStrategy());
  log(rabbit1.crossRiver()); // [log] ウサギは泳ぎました

  final rabbit2 = Rabbit(BridgeStrategy());
  log(rabbit2.crossRiver()); // [log] ウサギは橋を渡りました

  final rabbit3 = Rabbit(FordStrategy());
  log(rabbit3.crossRiver()); // [log] ウサギは浅瀬を渡りました
}

ウサギが川を渡る戦略を動的に切り替えてみます。

  • 体調が良く、時間がある場合は橋を作る
  • 時間がないけど体調が良ければ泳ぐ
  • 天気が良い場合に限り、浅瀬を探す

これらの判断で戦略を選びます。

状況クラスを作成しました。

// 状況
class Situation {
  final bool isHealthy; // 体調が良いか
  final bool hasTime; // 時間があるか
  final bool isGoodWeather; // 天気が良いか

  Situation(
      {this.isHealthy = false,
      this.hasTime = false,
      this.isGoodWeather = false});

  Rabbit selectStrategy() {
    // 体調が良く、時間がある場合は橋を渡る
    if (isHealthy && hasTime) {
      return Rabbit(BridgeStrategy());
      // 時間がないけど体調が良ければ泳ぐ
    } else if (isHealthy && !hasTime) {
      return Rabbit(SwimStrategy());
      // 天気が良い場合に限り、浅瀬を探す
    } else if (isGoodWeather) {
      return Rabbit(FordStrategy());
    } else {
      return Rabbit(SwimStrategy());
    }
  }
}

クライアントコードで状況に応じて戦略を変えるようにコードを書いてみます。

void main() {
  // 体調が良い・時間がある・天気が悪い
  Situation situation1 =
      Situation(isHealthy: true, hasTime: true, isGoodWeather: false);
  Rabbit rabbit1 = situation1.selectStrategy();
  log(rabbit1.crossRiver()); // [log] ウサギは橋を渡りました

  // 体調が悪い・時間がない・天気が良い
  Situation situation2 =
      Situation(isHealthy: false, hasTime: false, isGoodWeather: true);
  Rabbit rabbit2 = situation2.selectStrategy();
  log(rabbit2.crossRiver()); // [log] ウサギは浅瀬を渡りました

  // 体調が良い・時間がない・天気が悪い
  Situation situation3 =
      Situation(isHealthy: true, hasTime: false, isGoodWeather: false);
  Rabbit rabbit3 = situation3.selectStrategy();
  log(rabbit3.crossRiver()); // [log] ウサギは泳ぎました
}

状況に応じて柔軟に戦略を変えれることが分かります。

またStrategyクラスとRabbitクラスは疎結合になります。

つまり、片方を変更してももう一方への影響が低減します。特に新たな戦略を追加する場合、Rabbitクラスを修正する必要なく、新たなStrategyインターフェースの実装クラスを追加するだけで良いです。

ABOUT ME
なっとう
なっとう
Fluttter開発 プログラマー
噛み砕いて説明できるようになれば、プログラマーとしての質が上がるのではないかと思い、ブログを始めました。 このノウハウが誰かのお役に立てば嬉しいです。
記事URLをコピーしました