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

# AbstractFactoryパターンについて軽く
インスタンスの生成を担ってくれる専用のクラスを用意することで、そのインスタンスを使用したいクラスからインスタンス生成の責務を剥がすことができる。
また、関連インスタンスが複数存在する場合に、それらのインスタンス生成方法をまとめるクラスとして、言葉通りそれらを取りまとめるインスタンス生成の工場として稼働させることもできる。
前者としては [前回のFactoryMethodパターン](https://qiita.com/kenta4327/items/93ece56ee61a3526cfb9) も形は似ているが、FactoryMethodパターンはとある1種類のインスタンス生成の初期手順を定義してあげるパターンで、AbstractFactoryパターンは1つ複数、種類関係なく関連インスタンス生成の責務を担うパターン、という違いがあるように感じた。

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

[前回](https://qiita.com/kenta4327/items/93ece56ee61a3526cfb9#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%81%A8%E3%81%97%E3%81%A6%E4%BD%95%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%9F%E3%81%8B) と同じものを作る

> 保存するボタンと読み込むボタンがある画面で、
保存するボタンを押すと、テキストボックスにある文字列を保存して、
読み込むボタンを押すと、保存先から文字列を読み込んで出力する、
というアプリ。

ただし、前回はテキスト保存を扱うUtilクラスを1つしたが、
Writer、Readerに分け、それぞれを生成するAbstractFactoryクラスを作成する。

# クラス図

```mermaid
classDiagram
  class AbstractFactory {
    <<abstract>>
    +createTextWriter()
    +createTextReader()
  }
  class FileTextUtilFactory {
    +createTextWriter()
    +createTextReader()
  }
  class TextWriter {
    <<abstract>>
    +save()
  }
  class TextReader {
    <<abstract>>
    +read()
  }
  class FileTextWriter {
    +save()
  }
  class FileTextReader {
    +read()
  }

  AbstractFactory <|.. FileTextUtilFactory
  TextWriter <|.. FileTextWriter
  TextReader <|.. FileTextReader

  FileTextWriter <-- FileTextUtilFactory
  FileTextReader <-- FileTextUtilFactory
```

# コード

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

```dart:abstract_factory.dart
import 'package:flutter_abstract_factory/utils/text_reader.dart';
import 'package:flutter_abstract_factory/utils/text_writer.dart';

abstract class AbstractFactory {
  TextWriter createTextWriter();
  TextReader createTextReader();
}
```

```dart:file_text_util_factory.dart
import 'package:flutter_abstract_factory/utils/abstract_factory.dart';
import 'package:flutter_abstract_factory/utils/file_text_reader.dart';
import 'package:flutter_abstract_factory/utils/file_text_writer.dart';
import 'package:flutter_abstract_factory/utils/text_reader.dart';
import 'package:flutter_abstract_factory/utils/text_writer.dart';

class FileTextUtilFactory implements AbstractFactory {
  @override
  TextReader createTextReader() {
    return FileTextReader();
  }

  @override
  TextWriter createTextWriter() {
    return FileTextWriter();
  }
}
```

```dart:text_writer.dart
abstract class TextWriter {
  void save(text);
}
```

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

import 'package:flutter_abstract_factory/utils/text_writer.dart';
import 'package:path_provider/path_provider.dart';

class FileTextWriter implements TextWriter {

  @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);
  }
}
```

```dart:text_reader.dart
abstract class TextReader {
  Future<String> read();
}
```

```dart:file_text_reader.dart

import 'dart:io';

import 'package:flutter_abstract_factory/utils/text_reader.dart';
import 'package:path_provider/path_provider.dart';

class FileTextReader implements TextReader {

  @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 = '';
  TextWriter? _textWriter;
  TextReader? _textReader;

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

    final abstractFactory = FileTextUtilFactory();
    _textWriter = abstractFactory.createTextWriter();
    _textReader = abstractFactory.createTextReader();
  }
  :
  : // 割愛
  :
```

以上