デザインパターン攻略：Strategy編

業務内外に関わらずコーディングしている際に手数の少なさに悩んでいる今日この頃…。

新たな手札を手に入れるためにデザインパターンをマスターしたい！
今回はStrategyパターンをまとめていきます。

# Strategyパターンとは？
ひとことで言えば、
**アルゴリズムを実行時に選択できるようにするデザインパターン**
です

クラス図で表すとこんな感じ。（wikiから拝借しました）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3889500/2c786c7a-4f4f-4ef1-8f82-8b0e4f5297c3.png)

https://ja.wikipedia.org/wiki/Strategy_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3 


インターフェースの実装クラスを選択するだけで簡単にアルゴリズムを入れ替えることができるわけですね。

機能追加の際は`ConcreateStrategy`クラスの追加のみでOKです。
新規機能追加も既存コードの変更なく簡単に実現できます！

# メリット
では、Strategyパターンを利用するメリットは何か？
Perplexityに聞いてみました。

- **柔軟性の向上**
アルゴリズムを動的に切り替えることが可能で、異なる状況に応じた振る舞いを簡単に実現できます。
  
- **再利用性の向上**
同じアルゴリズムを異なるコンテキストで再利用でき、コードの重複を減らせます。

- **拡張性**
新しいアルゴリズムを簡単に追加でき、オープン・クローズド原則（既存コードの変更なしで拡張可能）を実現します。

- **保守性と疎結合**
アルゴリズムが独立しているため、特定の部分を変更しても他の部分に影響を与えません。

- **条件分岐の削減**
if/else や switch 文による複雑な条件分岐を削減し、コードの可読性と保守性を向上させます。

# 適用例
- 支払い処理（クレジットカード、PayPal、銀行振込など）
- ファイル処理（CSV, JSON, XMLなど異なるフォーマットへの対応）
- ナビゲーションアプリ（経路計算アルゴリズムの切り替え）

# とりあえず使ってみた
今回は趣味で触ったことのあるFlutter(言語はDart)でStrategyパターンの実装をしてみました。
ボタン押下時にStrategyを入れ替えて、選択されたStrategyに応じたメッセージを表示します。

```main.dart
// Flutterフレームワークのテンプレート（ここはStrategyパターンと関係ないです）
void main() {
  runApp(const UseStrategy());
}

class UseStrategy extends StatelessWidget {
  const UseStrategy({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'UseStrategy',
      debugShowCheckedModeBanner: false,
      home: Home(),
    );
  }
}
```
``` home.dart
class Home extends StatefulWidget {
  const Home({super.key});

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  late MessageCreator _messageCreator; // コンテキスト
  late String _message;

  @override
  void initState() {
    super.initState();
    // 初期表示時はボタンごとのメッセージを表示しないが、_contextの初期化が必須のためAで初期化しておく
    _messageCreator = MessageCreator(StrategyA());
    // 初回表示時のメッセージ
    _message = "ボタンを押してみよう！";
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Strategyパターン"),
      ),
      body: Column(
        children: [
          Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                // A
                ElevatedButton(
                    onPressed: () => _pressedButton(StrategyA()),
                    child: const Text("A")),
    
                // B
                ElevatedButton(
                    onPressed: () => _pressedButton(StrategyB()),
                    child: const Text("B")),
    
                //　C
                ElevatedButton(
                    onPressed: () => _pressedButton(StrategyC()),
                    child: const Text("C")),
              ],
          ),

          // 表示メッセージ
          Text(_message)
        ],
      ),
    );
  }

  /// ボタン押下時の一連処理
  void _pressedButton(ISelectedValueStrategy strategy) {
    setState(() {
      // 押下されたボタンに応じたStrategyクラスを設定
      _messageCreator.setStrategy(strategy);
      // 表示メッセージの変更
      _message = _messageCreator.execute();
    });
  }
}
```
```strategy_components.dart
/// Strategyインターフェースクラス
abstract interface class ISelectedValueStrategy {
  String createMessage();
}

/// Strategyによって異なる振る舞いを実行するコンテキストクラス
class MessageCreator {
  ISelectedValueStrategy _strategy;

  MessageCreator(this._strategy);

  void setStrategy(ISelectedValueStrategy strategy) {
    _strategy = strategy;
  }

  String execute() {
    return _strategy.createMessage();
  }
}

class StrategyA implements ISelectedValueStrategy {
  @override
  String createMessage() => "Aが選択されました!";
}

class StrategyB implements ISelectedValueStrategy {
  @override
  String createMessage() => "Bが選択されました!";
}

class StrategyC implements ISelectedValueStrategy {
  @override
  String createMessage() => "Cが選択されました!";
}
```

## 画面表示

![Simulator Screen Recording - iPhone 16 - 2025-03-24 at 08.46.04.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3889500/5923a208-8fdc-49d8-ae77-86c30b4cb247.gif)

ボタン押下で表示文字列が変わるようになってます！

ボタンを追加したい場合はUI部分と`ISelectedValueStrategy`の実装クラスを追加するだけでOK👍

既存機能に干渉しないため、機能追加・変更のリスクを最小限に抑えることができます。

# 参考
https://rookie-programmer.jp/?p=142

https://zenn.dev/xurenjun/articles/0e0474530841b8