Commandパターンについて調べたのでFlutterでサンプルを作ってみた

# Commandパターンについて軽く
ファイルに書き込む、DBに保存する、といった処理をCommandクラスとして切り出し、処理数の増減や内容の変更などへの拡張性を高めたパターン。
構造自体は昨日書いた [Observerパターン](https://qiita.com/kenta4327/items/d5600ffb3bf446a22a69) とほぼ同じ。
実際の処理はReceiverで行い、Commandの実行が呼ばれたら(execute())、CommandからReceiverの処理を呼び出す(action())。(Receiverは無しでCommandがそのまま処理をするのもありかも)
メソッドとして処理を切り出すのとの違いは、オブジェクトなのでキューなどに貯めやすいところだと思われる。

# サンプルとして何を作ったか
`Observerパターン` とほぼ同じ構造なので、 [Observerパターン](https://qiita.com/kenta4327/items/d5600ffb3bf446a22a69) と同じもの

> 新しくFlutterプロジェクトを作成するときに自動生成されるカウンターアプリを元に、カウントがインクリメントされたらファイル書き込みとDB書き込みするように変更し、その通知をObserverパターンを用いて行う。
(サンプルコードとしてはファイル書き込み、DB書き込みを行った、というていのログのみ記載)

をCommandパターンを用いて作ってみる。

# クラス図

```mermaid
classDiagram
  class Command {
    <<abstract>>
    +execute();
  }
  class FileWriteCommand {
    +execute();
  }
  class DbWriteCommand {
    +execute();
  }
  class Receiver {
    <<abstract>>
    +action();
  }
  class FileWriteReceiver {
    +action();
  }
  class DbWriteReceiver {
    +action();
  }
  class _MyHomePageState {
    +addCommand();
    +removeCommand();
    +execute();
  }
  class MyHomePage {
  }
  class MyApp {
  }

  Command <|.. FileWriteCommand
  Command <|.. DbWriteCommand
  Receiver <|.. FileWriteReceiver
  Receiver <|.. DbWriteReceiver

  MyHomePage <-- MyApp
  _MyHomePageState <-- MyHomePage
  FileWriteCommand <-- _MyHomePageState
  DbWriteCommand <-- _MyHomePageState
  FileWriteReceiver <-- FileWriteCommand
  DbWriteReceiver <-- DbWriteCommand
```

# コード

[GitHub](https://github.com/kenta4327/flutter_command) に完全版があります。

```dart:command.dart
abstract class Command {
  void execute(int counter);
}
```

```dart:file_write_command.dart
import 'package:flutter_command/commands/command.dart';
import 'package:flutter_command/receivers/file_write_receiver.dart';

class FileWriteCommand implements Command {
  final _fileWriteReceiver = FileWriteReceiver();

  @override
  void execute(counter) {
    _fileWriteReceiver.action(counter);
  }
}
```

```dart:db_write_command.dart
import 'package:flutter_command/commands/command.dart';
import 'package:flutter_command/receivers/db_write_receiver.dart';

class DbWriteCommand implements Command {
  final _dbWriteReceiver = DbWriteReceiver();

  @override
  void execute(counter) {
    _dbWriteReceiver.action(counter);
  }
}
```

```dart:receiver.dart
abstract class Receiver {
  void action(int counter);
}
```

```dart:file_write_receiver.dart
import 'package:flutter_command/receivers/receiver.dart';

class FileWriteReceiver implements Receiver {
  @override
  void action(counter) {
    // とりあえずログ記載のみ行う
    print('$counter をファイルに書き込み');
  }
}
```

```dart:db_write_receiver.dart
import 'package:flutter_command/receivers/receiver.dart';

class DbWriteReceiver implements Receiver {
  @override
  void action(counter) {
    // とりあえずログ記載のみ行う
    print('$counter をDBに書き込み');
  }
}
```

```dart:my_home_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_command/commands/command.dart';
import 'package:flutter_command/commands/db_write_command.dart';
import 'package:flutter_command/commands/file_write_command.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  final List<Command> _commands = [];

  void addCommand(Command command) {
    _commands.add(command);
  }

  void removeCommand(Command command) {
    _commands.remove(command);
  }

  void execute() {
    for (final command in _commands) {
      command.execute(_counter);
    }
  }

  @override
  void initState() {
    addCommand(FileWriteCommand());
    addCommand(DbWriteCommand());

    super.initState();
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
      execute();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
```

以上