デザインパターン (Singletonパターン)

## デザインパターンとは
デザインパターン(設計パターン）とは、過去のソフトウェア設計者が発見し編み出した設計ノウハウに名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したものです。

デザインパターンを利用するメリットは、**再利用性の高い柔軟な設計ができる**という点です。設計者の直感や経験などに依存していた設計が、デザインパターンを導入することで、初心者でも先人たちが詰め込んだ「知恵」を利用して設計をすることが可能になります。

もう一つのメリットとして、**技術者どうしの意思疎通が容易になる**ことが挙げられます。デザインパターンを習得している技術者どうしであれば、パターン名で設計の概要の合意を取ることが可能になります。このように、デザインパターンを学習しておくことで開発者どうしの意思疎通がスムーズになるのです。

以下は、Flutterにおける主要なデザインパターンです。

- Model-View-Controller (MVC)
- Model-View-ViewModel (MVVM)
- Provider(プロバイダー) パターン
- Bloc(ブロック) パターン
- Singleton(シングルトン) パターン
- Factory(ファクトリー) パターン
- Builder(ビルダー) パターン
- Composite(コンポジット) パターン

これらのパターンを駆使することで、開発者はアプリケーションの品質を向上させ、開発をスムーズに進めることができ、プロジェクトの規模やメンバーのスキルに応じた柔軟な設計が行えるのです。

今回は **Singletonパターン** に対象を絞って解説をしていきます。

## Singletonパターン
Singleton (シングルトン)とは、あるクラスのインスタンスがアプリケーション全体で、
たった一つしか存在しないことを保証するためのデザインパターンです。

例えば、アプリケーション全体で共通の設定やログ管理、データベース接続など、
状態を共有する必要がある場合に使用されます。

### 1. 定義 
Singletonパターンでは、クラスがインスタンス化される際に、
そのクラスの唯一のインスタンスを返すように設計されます。

#### 特徴
- インスタンスが一度しか作成されない。
- 作成されたインスタンスは、どこからでもアクセスできる。
- クラス自体がその唯一のインスタンスへのアクセスを管理する。

### 2. コンポーネント

- **Singletonクラス:** 唯一のインスタンスを生成し、管理するクラス。

- **privateコンストラクタ:** 外部からインスタンスを生成できないようにするために使用されるコンストラクタ。クラスのコンストラクタをプライベートにすることで、外部から直接インスタンス化できないようにします。

- **staticメソッド:** クラスの唯一のインスタンスを取得するために使用される。


### 3. メリット

- **インスタンスの一貫性:** インスタンスが一つしか存在しないため、データの整合性を保つことができます。

- **リソースの節約:** インスタンスが複数作成されないため、メモリやその他のリソースの使用を最小限に抑えることができます。

- **グローバルアクセス:** アプリケーション全体で同じインスタンスにアクセスできます。

- **オーバーヘッドの削減:** 複数のインスタンス生成によるメモリ消費を抑えることができます。

- **状態の共有:** アプリケーション全体で状態を共有できます。

### 4. サンプルコード

```dart: Singletonサンプルコード
class Singleton {
  // クラス内で一つのインスタンスを保持
  static final Singleton _instance = Singleton._internal();

  // プライベートコンストラクタ
  Singleton._internal();

  // インスタンスを取得するためのファクトリコンストラクタ
  factory Singleton() {
    return _instance;
  }

  // サンプルメソッド
  void someMethod() {
    print("This is a method from the Singleton instance.");
  }
}

void main() {
  // Singletonインスタンスを取得
  var s1 = Singleton();
  var s2 = Singleton();

  // インスタンスが同一であることを確認
  print(s1 == s2); // true

  // Singletonのメソッドを呼び出し
  s1.someMethod();
}
```
- **静的インスタンス (_instance):**

```dart
static final Singleton _instance = Singleton._internal();
```

Singletonクラスの唯一のインスタンスを作成しています。 `_instance` は `final` で定義しているので、一度作成されたら変更されることはありません。

:::note warn
**static キーワード** は、この変数がクラスそのものに属することを意味します。
つまり、 `_instance` は クラスレベルのフィールド であり、クラスのインスタンス（オブジェクト）ごとではなく、クラス自体に対して一つだけ存在します。
:::

シングルトンパターンでは、クラス全体で一つだけのインスタンスを保持したいので、このインスタンスをクラスに結びつけるために `static` を使います。


#### staticを付与しない場合

もしこの `static` を外した場合、以下のようなことが起こります

- **コンパイルエラー：**
`final Singleton _instance = Singleton._internal();` とすると、
この行自体でコンパイルエラーが発生します。なぜなら、インスタンス変数の初期化にはインスタンスメソッド（この場合は `_internal()` ）を使用できないからです。

- **インスタンスレベルのフィールドになる：**
`static` を付与しない場合、 `_instance` はクラスのインスタンスごとに生成されます。つまり、`Singleton` クラスの新しいインスタンスを作成するたびに、新しい `_instance` 変数が生成され、異なるインスタンスが保持されます。

- **シングルトンの目的が達成されない：**
シングルトンパターンは、クラスのインスタンスを一つだけに制限するためのものです。 `static` を付けないと、各インスタンスごとに異なる `_instance` が作成されるため、クラスのインスタンスが複数存在する可能性が出てきます。これは、シングルトンパターンの本来の目的である「一つのインスタンスのみを共有する」という目的が達成されなくなります。


- **プライベートコンストラクタ (_internal):**

```dart
Singleton._internal();
```

:::note info
_internal() は、コンストラクタをプライベートアクセスにしています。 _internal() という名前は慣例的なものであって、必ずしもこの名前である必要はありません。極端な話 _() などでも構いません。
:::


このコンストラクタはプライベートなので、クラスの外部から直接呼び出すことはできません。これにより、クラスの外部で新しいインスタンスを作成することが防止されます。

- **ファクトリコンストラクタ:**

```dart
factory Singleton()
```

ファクトリコンストラクタは、クラスのインスタンスを返すために使用されます。この場合、すでに作成された静的インスタンス（`_instance`）を返すようにしています。これにより、呼び出しごとに新しいインスタンスを作成するのではなく、常に同じインスタンス(既に存在する `_instance` )が返されます。

:::note info
**コンストラクタについて**
クラスのインスタンスが生成される際に、自動的に呼び出される初期化処理のことを指します。
:::


:::note info
**factoryキーワードについて**
コンストラクタが呼ばれたオブジェクトを常に作成するのではなく、キャッシュがあればキャッシュを使うことができるようになります。このキーワードがコンストラクタの前に記載されていない場合、コンストラクタで値をreturnすることはできません。
:::

- **サンプルメソッド (someMethod):**

```dart
void someMethod() {...}
```

このメソッドは、Singletonインスタンスから呼び出されるサンプルのメソッドです。単純にメッセージをコンソールに表示します。

- **インスタンスの取得 (s1とs2):**

```dart
var s1 = Singleton();
var s2 = Singleton();
``` 
s1とs2はどちらもSingletonクラスのインスタンスを取得しています。しかし、ファクトリコンストラクタを通じて取得されるため、実際には同じインスタンス（`_instance`）が返されます。

- **インスタンスの比較 (s1 == s2):**

```dart
print(s1 == s2); // true
```

s1とs2が同一のインスタンスであることを確認するためのコードです。ここでは、trueが出力され、同じインスタンスであることが証明されます。

- **メソッドの呼び出し (someMethod):**

```dart
s1.someMethod();
```

s1からsomeMethodメソッドを呼び出しています。これにより、"This is a method from the Singleton instance."というメッセージがコンソールに表示されます。

### 5. 普通のクラス vs シングルトンクラス
ではシングルトンクラスを理解するために、普通のクラスとSingletonクラスの違いを
確認してみましょう。今回は例として、ログを確認する際に、
使用するLoggerクラスを元に説明していきます。

#### 普通のクラス Logger
まず、Singletonパターンを使用しない場合のログクラスを見てみましょう。
Loggerクラスは、複数のインスタンスが作成でき、それぞれが独自のログを
保持します。

```dart: Singletonパターンを使用しない場合
class Logger {
  Logger();

  List<String> logs = [];

  void addLog(String log) {
    logs.add(log);
    print('Log added: $log');
  }

  List<String> getLogs() {
    return logs;
  }
}
```

このLoggerクラスを使って、ログを追加するコードは以下の通りです。

```dart
void main() {
  final logger1 = Logger();
  logger1.addLog('User logged in'); 
  logger1.addLog('User viewed profile'); 
  print(logger1.getLogs()); // [User logged in, User viewed profile]
  
  final logger2 = Logger();
  logger2.addLog('User logged out'); 
  print(logger2.getLogs()); // [User logged out]
}
```

上記のコードでは、 `logger1` と `logger2` はそれぞれ別々のLoggerインスタンスを
持ちます。そのため、 `logger1` が保持するログと `logger2` が保持するログは異なり、
ログの管理が分散されています。これは、異なるインスタンスが独立してログを
保持しているためです。

#### シングルトンクラス SingletonLogger
次に、Singletonパターンを使用したSingletonLoggerクラスを見てみましょう。
このクラスでは、アプリケーション全体で唯一のインスタンスが使用され、
すべてのログが一元管理されます。

```dart
class SingletonLogger {
  static final SingletonLogger _instance = SingletonLogger._internal();
  List<String> logs = [];

  factory SingletonLogger() {
    return _instance;
  }

  SingletonLogger._internal();

  void addLog(String log) {
    logs.add(log);
    print('Log added: $log');
  }

  List<String> getLogs() {
    return logs;
  }
}
```

このSingletonLoggerクラスを使って、ログを追加するコードは以下の通りです。

```dart
void main() {
  final logger1 = SingletonLogger();
  logger1.addLog('User logged in'); 
  logger1.addLog('User viewed profile'); 
  print(logger1.getLogs()); // [User logged in, User viewed profile]
  
  final logger2 = SingletonLogger();
  logger2.addLog('User logged out'); 
  print(logger2.getLogs()); // [User logged in, User viewed profile, User logged out]
}
```

SingletonLoggerクラスでは、`logger1` と `logger2` が同じインスタンスを
共有しているため、ログが一つのリストに集約され、
ログの一貫性が保たれ、管理が容易になります。

Singletonパターンを使用することで、リソースや状態の一貫性を
保ちながら効率的に管理することができます。

### 6. Tips
一般的には②が利用されることが多いですが、
シングルトンの書き方にはいろんなパターンが存在します。

#### ①「staticのクラス変数」と「privateコンストラクタ」

```dart
class Singleton {
  
  // instanceはprivateではない
  static final Singleton instance = Singleton._internal(); 
  
  Singleton._internal();
}

void main() {
  Singleton singleton = Singleton.instance;
}
```


#### ②「staticのprivateクラス変数」と「factoryコンストラクタ」と「privateコンストラクタ」

```dart
class Singleton {
  
 static final Singleton _instance = Singleton._internal();
  
  factory Singleton() {
    return _instance;
  }
  
  Singleton._internal();
}

void main() {
  Singleton singleton = Singleton();
}
```

```dart
class Singleton {

  static Singleton? _instance; //null safetyのprivateクラス変数
  
  factory Singleton() {  
    //_instanceがnullであればSingleton._internal()でインスタンス化したものをreturn
    //_instanceがnullでなければ既にキャッシュしている_instanceをreturn
    return _instance ??= Singleton._internal(); 
    //１回目は_instanceがnullなのでSingleton._internal()がreturnされる
    //２回目以降は、_instanceがnullでないので_instanceがreturnされる
  }
  
  Singleton._internal();
}

void main() {
  Singleton singleton = Singleton();
}
```

#### ③「staticのprivateクラス変数」と「privateコンストラクタ」と「getter」

```dart
class Singleton {
  
  static final Singleton _instance = Singleton._internal();
  
  Singleton._internal();
  
  static Singleton get instance => _instance;
  
}

void main() {
  Singleton singleton = Singleton.instance;
}
```

## まとめ
Singletonパターンは、アプリケーション全体で一つのインスタンスしか
存在しないクラスを作成するためのデザインパターンです。

これにより、リソースの節約や状態の一貫性を保つことができます。
Dartでは、プライベートコンストラクタと静的プロパティを使って
Singletonパターンを実装します。

このパターンを適切に利用することで、特定のクラスのインスタンスを
効率的に管理できるようになるので、これを機にぜひ活用してみてください！

## 参考資料

https://note-tmk.hatenablog.com/entry/2022/08/18/231752

https://zenn.dev/makumaaku/articles/flutter-singleton

https://note.com/saburo_engineer/n/n564d0dc659ae

https://flutter.keicode.com/basics/data-store-singleton.php

## 告知
最後にお知らせとなりますが、イーディーエーでは一緒に働くエンジニアを
募集しております。詳しくは採用情報ページをご確認ください。

https://eda-inc.jp/recruit/

みなさまからのご応募をお待ちしております。