すらぼうの開発ノート

モバイルアプリエンジニアのメモ

【Flutter / Dart】Singleton(シングルトン)パターンの実装例

本エントリではデザインパターンの一つであるSingletonパターンのFlutterにおける実装例を紹介する。

Singletonパターンとは

あるクラスのインスタンスが一つだけ生成されるデザインパターンのこと。

インスタンスが一つだけしか存在しないため、インスタンスが管理する変数などへのアクセス経路も制御が容易になる。

使用ケース

個人的に思うSingletonパターンのメリットは以下である。

  • メモリの使用量を削減できる
  • データの管理場所を一つにできる

上記のメリットのため、以下のようなケースで使用することが多い。

  • サイズが大きいインスタンスを生成するケース
  • 設定情報やログ情報など、情報を単一にまとめることに意味のあるケース

使用する際の注意

メリットも多いデザインパターンだが、状況によってはアンチパターンと考えられる場合もある。利用する際には注意してほしい。

例えば以下の議論。 stackoverflow.com

アンチパターンとして扱われている理由としては例えば「インスタンスが一つなので、モックのインスタンスが作成できずunit testがやりにくい」、といった点が挙げられる。


実装例

例としてUIの設定情報管理するクラスをSingletonクラスで実装する。

作成するクラスは設定項目を持たせる。

  • 言語(日本語or英語)
  • ダークモード(有効or無効)
  • 文字サイズ(10 ~ 30の整数)

コード

作成したクラスは以下。

class SettingInfo {
  late String language;
  late bool isDarkModeEnabled;
  late int fontSize;

  static final SettingInfo _instance = SettingInfo._internal();

  factory SettingInfo() {
    return _instance;
  }

  SettingInfo._internal() {
    language = "ja";
    isDarkModeEnabled = false;
    fontSize = 16;
  }
}

ポイント

実装上のポイントは以下。

  • factoryキーワードをコンストラクタの前に記述する
  • _internal()コンストラクタを用意する(_をつけてprivateにする)
  • クラス内で生成するインスタンスはprivateにする

factoryキーワードをコンストラクタの前に記述する

factoryキーワードを使用することでコンストラクタが呼ばれた際に既存のインスタンスを返却している。

そのため一度SettingInfoインスタンスが生成されると、以降の呼び出しでは既存のインスタンスが返却されるようになる。 結果として何度コンストラクタを呼ばれてもアプリケーション内でインスタンスを一つにすることができる。

factoryキーワードについて

factoryキーワードは必ずしもコンストラクタが呼ばれた際に毎回新しいインスタンスを返すわけではない場合に使用する。このキーワードがコンストラクタの前に記載されていない場合、コンストラクタで値をreturnすることはできない。

dart.dev

_internal()コンストラクタを用意する

クラス内で使用する名前付きコンストラクタを用意する。分かりやすく_internal()命名することが多い。 このコンストラクタはprivateにするため先頭にアンダーバーを記述する。

クラス内で生成するクラスはprivateにする

クラス外からのインスタンス取得は全てfactoryコンストラクタを経由させたいので、クラス内に生成したインスタンスはprivateにする必要がある。 そこでインスタンス名の先頭にアンダーバーを記述する。

まとめ

Singletonパターンとはあるクラスのインスタンスが一つだけ生成されるデザインパターンのことである。

Singletonパターンは以下のメリットがある。

  • メモリの使用量を削減できる
  • データの管理場所を一つにできる

そのため以下のケースで使用すると便利である。

  • サイズが大きいインスタンスを生成するケース
  • 設定情報やログ情報など、情報を単一にまとめることに意味のあるケース

実装上のポイントは以下。

  • factoryキーワードをコンストラクタの前に記述する
  • _internal()コンストラクタを用意する(_をつけてprivateにする)
  • クラス内で生成するインスタンスはprivateにする