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

# FactoryMethodパターンについて軽く
オブジェクトの作成手順を抽象クラスとして定義し、作成するオブジェクトの種類を切り替えても、作成元は作成手順を意識しなくて良いようにするパターン。
例えば、データをアウトプットするために、ファイル書き込み用のオブジェクトを生成するとする。
また、オブジェクト生成時には、どこに書き込むか出力する、という手順があったとする。
そのような手順を定義しておくことで、ファイル書き込みからDB書き込みに変更した、となったときも、生成対象のオブジェクトを変更するだけで、初期手順については意識せずに対応できる。

# サンプルとして何を作ったか

保存するボタンと読み込むボタンがある画面で、
保存するボタンを押すと、テキストボックスにある文字列を保存して、
読み込むボタンを押すと、保存先から文字列を読み込んで出力する、
というアプリ。
テキスト保存用のUtilクラス作成部分をFactoryMethodパターンにした。

# クラス図

```mermaid
classDiagram
  class TextUtilCreator {
    <<abstract>>
    +create()
    +factoryMethod()
    +printGenerationComplete()
  }
  class FileTextUtilCreator {
    +factoryMethod()
    +printGenerationComplete()
  }
  class TextUtil {
    <<abstract>>
    +save()
    +read()
  }
  class FileTextUtil {
    +save()
    +read()
  }
  class MyApp {
  }
  class MyHomePage {
  }
  class _MyHomePageState {
  }

  TextUtilCreator <|-- FileTextUtilCreator
  TextUtil <|-- FileTextUtil

  TextUtil <-- TextUtilCreator
  FileTextUtil <-- FileTextUtilCreator

  FileTextUtilCreator <-- _MyHomePageState
  _MyHomePageState <-- MyHomePage
  MyHomePage <-- MyApp
```

# コード

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

```dart:text_util_creator.dart
import 'package:flutter_factory_method/utils/text_util.dart';

abstract class TextUtilCreator {
  TextUtil create() {
    final textUtil = factoryMethod();
    printGenerationComplete();
    return textUtil;
  }

  TextUtil factoryMethod();
  void printGenerationComplete();
}
```

```dart:file_text_util_creator.dart
import 'package:flutter_factory_method/utils/file_text_util.dart';
import 'package:flutter_factory_method/utils/text_util.dart';
import 'package:flutter_factory_method/utils/text_util_creator.dart';

class FileTextUtilCreator extends TextUtilCreator {
  @override
  TextUtil factoryMethod() {
    return FileTextUtil();
  }

  @override
  void printGenerationComplete() {
    print('FileTextUtilCreator を生成しました');
  }
}
```

```dart:text_util.dart
abstract class TextUtil {
  void save(text);
  Future<String> read();
}
```

```dart:file_text_util.dart
import 'dart:io';

import 'package:flutter_factory_method/utils/text_util.dart';
import 'package:path_provider/path_provider.dart';

class FileTextUtil extends TextUtil {

  @override
  void save(text) async {
    final directory = await getApplicationDocumentsDirectory();
    final path = directory.path;
    final file = File('$path/hoge.txt');

    // ファイルがなかったら生成する
    if (!await file.exists()) {
      await file.create();
    }
    await file.writeAsString(text);
  }

  @override
  Future<String> read() async {
    final directory = await getApplicationDocumentsDirectory();
    final path = directory.path;
    final file = File('$path/hoge.txt');

    // ファイルがあった時だけ読み込む
    if (await file.exists()) {
      final data = await file.readAsString();
      return data;
    }

    return '';
  }
}
```

```dart:main.dart
  :
  : // 割愛
  :
class _MyHomePageState extends State<MyHomePage> {
  final _textController = TextEditingController();
  String _outputText = '';
  TextUtil? _textUtil;

  @override
  void initState() {
    super.initState();

    // FileTextUtilCreatorのcreateメソッドを呼び出しているだけで、
    // 扱う対象がファイルじゃなくなったとしても対象クラスを変更するだけで、作成手順については触れなくて良い
    final textUtilCreator = FileTextUtilCreator();
    _textUtil = textUtilCreator.create();
  }
  :
  : // 割愛
  :
```

以上