🤖

Singleton/Factory

に公開

Singleton Pattern

アプリケーション実行中にクラスのインスタンスが1つだけ生成されることを保証するデザインパターン。あまり使い方の例が紹介されていない。

普段はRiverpodばかり使っているので使うことは研究するときぐらいなのですが、あまりパッケージに頼らないで、オブジェクト思考でコード書いたりメソッドチャンネルを活用している企業では使ってそうだなと思いチュートリアルで出てきた内容だけ試してみた。

昨日面談した企業は、めずらしくRiverpodあまり使わない、go_router_builderauto_routeも使ってないのでカルチャーショックが強かった😅

メリット

  • リソース節約: メモリ使用利用が削減される
  • 一貫性: アプリケーション全体で一貫した状態が維持される。(例: アプリケーションの設定や環境変数)
  • グローバルアクセスポイント: アプリケーションのどこからでもアクセス可能。

ユースケース

  • 共通ユーティリティ機能の提供: アプリケーション全体で再利用可能な共通のユーティリティ機能の提供
  • 設定情報の管理: アプリケーション全体で共有される設定や環境変数。
  • データベース接続: データベースへの接続を一元管理。
  • ロギング: アプリケーション全体からのログを一つのログファイルに書き込む。

実装上のポイント

  • プラベートコンストラクタ: クラスの外部からのインスタンス化を防ぐ。
  • 静的インスタンス: クラス内に唯一の静的インスタンスを持つ。
  • ファクトリコンストラクタ: 常に同じインスタンスを返す。

Dartの実装例

Dartのプロジェクトを作成してパッケージを追加すると実験できる。

dart create cookbook

https://pub.dev/packages/dotenv

add

dart pub add dotenv

.envを作成する。Flutterのプロジェクトみたいにpubspec.yamlの設定はしなくても良いみたいだ。

.env

apiKey = 1x00apikey
secretKey = 1x00secretKey

設定情報の管理のためのSingletonクラス

設定情報の管理
import 'package:dotenv/dotenv.dart';

/// 環境設定を管理するシングルトンクラス
class Settings {
  // プライベートなコンストラクタ
  Settings._internal() {
    // .envファイルを読み込む
    _env = DotEnv()..load();
    // 環境変数を取得
    apiKey = _env['apiKey'] ?? '';
    secretKey = _env['secretKey'] ?? '';
  }

  // シングルトンインスタンス
  static final Settings _instance = Settings._internal();

  /// シングルトンインスタンスを取得するファクトリコンストラクタ
  factory Settings() => _instance;

  // 環境変数
  late final DotEnv _env;
  
  /// APIキー
  late final String apiKey;
  
  /// シークレットキー
  late final String secretKey;
}

/// メイン関数
void main() {
  final settings = Settings();
  print(settings.apiKey); // 1x00apikey
  print(settings.secretKey); // 1x00secretKey
}

ロギングのためのSingletonクラス

ログを出すクラスを作っみた。プロジェクトだとパッケージを使うと思うが。使ったことがあるパッケージを参考までに記載しておく。

https://pub.dev/packages/logger
https://pub.dev/packages/talker

Logger Class
import 'dart:io';
import 'package:intl/intl.dart';

/// アプリケーション全体のログを管理するシングルトンクラス
class Logger {
  // プライベートコンストラクタ
  Logger._internal() {
    _logFile = File('app.log');
    if (!_logFile.existsSync()) {
      _logFile.createSync();
    }
  }

  // シングルトンインスタンス
  static final Logger _instance = Logger._internal();

  /// シングルトンインスタンスを取得するファクトリコンストラクタ
  factory Logger() => _instance;

  late final File _logFile;

  /// ログを記録する
  void info(String message) {
    final timestamp = DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now());
    final logMessage = '[$timestamp] INFO: $message\n';
    _logFile.writeAsStringSync(logMessage, mode: FileMode.append);
  }
}

/// メイン関数
void main() {
  final settings = Settings();
  print(settings.apiKey); // 1x00apikey
  print(settings.secretKey); // 1x00secretKey
  
  final logger = Logger();
  logger.info('アプリケーションが起動しました');
  
  try {
    throw Exception('テストエラー');
  } catch (e) {
    logger.info('エラーが発生しました: $e');
  }
}

最後に

おそらくパッケージをあまり使わない企業は、標準機能だけで頑張っているのだろうと思った。果たしどこまでできるのだろうか....

データベースで使用する例だと過去に書いた記事を参考にしてもらえると良いかも。
https://zenn.dev/joo_hashi/articles/af5a450cd3ba10

Discussion