Featured image of post 第5回-これがSingletonパターンだ! — 設定ファイルマネージャーを作ってみよう

第5回-これがSingletonパターンだ! — 設定ファイルマネージャーを作ってみよう

これまで実装してきた仕組みがGoFデザインパターンの「Singletonパターン」であることを解説。パターンの利点と注意点も紹介します。

@nqounetです。

前回は、instance()メソッドを実装して、インスタンスを1つに統一する仕組みを作りました。

今回は、この仕組みが何と呼ばれるのかを解説します。

ミキさんの疑問

ミキさんが聞いてきました。

instance()メソッドで1つのインスタンスを共有する仕組み、すごく便利だね!これって何か名前があるの?」

はい、あります。これはSingletonパターン(シングルトンパターン)と呼ばれる、有名なデザインパターンの1つです。

デザインパターンとは

デザインパターンとは、ソフトウェア設計における「よくある問題」と「その解決策」をパターン化したものです。

1994年に出版された「Design Patterns: Elements of Reusable Object-Oriented Software」という書籍で、23のパターンが紹介されました。著者4人は「Gang of Four(GoF)」と呼ばれ、これらのパターンは「GoFのデザインパターン」として広く知られています。

Singletonパターンの定義

Singletonパターンは、GoFの23パターンの1つで、以下のように定義されています。

クラスのインスタンスが1つだけ存在することを保証し、そのインスタンスへのグローバルなアクセスポイントを提供する

まさに、私たちが前回実装したConfigクラスの仕組みですね!

Singletonパターンの構造

Singletonパターンの構造を図で見てみましょう。

	classDiagram
    class Singleton {
        -instance$ Singleton
        -Singleton()
        +instance()$ Singleton
        +operation()
    }
    note for Singleton "instance()メソッドで\n唯一のインスタンスを返す"

構成要素を確認しましょう。

要素役割私たちの実装
instance(クラス変数)唯一のインスタンスを保持state $instance
instance()メソッドインスタンスを返す(なければ作成)sub instance
プライベートコンストラクタ外部からのnewを防ぐ(未実装)

私たちの実装を振り返る

私たちが実装したConfigクラスを見てみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package Config {
    use Moo;

    has _settings => (is => 'ro', default => sub { {} });

    # Singletonパターンの核心部分
    sub instance ($class) {
        state $instance;
        if (!$instance) {
            $instance = $class->new();
        }
        return $instance;
    }

    # 以下、設定管理のメソッド...
};

このinstance()メソッドが、Singletonパターンの核心です。

  1. state $instanceで唯一のインスタンスを保持
  2. 初回呼び出し時にnewでインスタンスを作成
  3. 2回目以降は既存のインスタンスを返す

Singletonパターンの利点

1. インスタンスが1つだけであることを保証

設定やログなど、アプリケーション全体で共有すべきリソースを確実に1つに保てます。

2. グローバルアクセスポイントの提供

どこからでもConfig->instance()で同じインスタンスにアクセスできます。引数で渡し続ける必要がありません。

3. 遅延初期化

instance()が初めて呼ばれるまでインスタンスは作成されません。リソースを節約できます。

Singletonパターンの注意点

Singletonパターンは便利ですが、注意点もあります。

1. テストが難しくなる場合がある

グローバルな状態を持つため、テスト間で状態がリセットされないことがあります。テスト用に設定をクリアするメソッドを用意するなどの工夫が必要です。

1
2
3
4
# テスト用に設定をクリアする例
sub clear_settings ($self) {
    %{$self->_settings} = ();
}

2. 依存関係が見えにくくなる

Config->instance()を直接呼ぶと、クラス間の依存関係がコードから読み取りにくくなります。

3. 過度な使用は避ける

「便利だから」という理由で何でもSingletonにすると、設計が複雑になります。本当に1つだけのインスタンスが必要な場合に限定しましょう。

Singletonパターンが適している場面

Singletonパターンは、以下のような場面で使うと効果的です。

場面理由
設定管理(Config)アプリ全体で同じ設定を共有したい
ログ出力(Logger)ログファイルのハンドルを1つにしたい
データベース接続(DB)コネクションを使い回したい
キャッシュ(Cache)キャッシュデータを共有したい

シリーズを振り返る

全5回のシリーズを振り返ってみましょう。

学んだこと
第1回ハードコードされた設定値をクラスで管理
第2回外部ファイルから設定を読み込む
第3回複数箇所から設定を使う問題が発覚
第4回instance()メソッドで問題を解決
第5回これがSingletonパターンだ!

普通にプログラムを作っていたら、自然とデザインパターンにたどり着きました。デザインパターンは、先人たちが試行錯誤の末に見つけた「良い設計」のエッセンスなのです。

第5回 完成コード

最終的な完成コードは、第4回と同じです。

ファイル構成

1
2
3
.
├── app.pl
└── config.ini

app.pl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
use v5.36;
use Moo;

package Config {
    use Moo;

    has _settings => (is => 'ro', default => sub { {} });

    # Singletonパターン: instance()メソッド
    sub instance ($class) {
        state $instance;
        if (!$instance) {
            $instance = $class->new();
        }
        return $instance;
    }

    sub load_config ($self, $file) {
        open my $fh, '<', $file or die "Cannot open $file: $!";
        while (my $line = <$fh>) {
            chomp $line;
            next if $line =~ /^\s*$/;
            next if $line =~ /^\s*#/;

            if ($line =~ /^\s*(\w+)\s*=\s*(.+?)\s*$/) {
                my ($key, $value) = ($1, $2);
                $self->set($key, $value);
            }
        }
        close $fh;
    }

    sub set ($self, $key, $value) {
        $self->_settings->{$key} = $value;
    }

    sub get ($self, $key) {
        return $self->_settings->{$key};
    }
};

package Logger {
    use Moo;
    use v5.36;

    sub debug ($self, $message) {
        my $config = Config->instance();

        if ($config->get('debug')) {
            say "[DEBUG] $message";
        }
    }
};

package main;

my $config = Config->instance();
$config->load_config('config.ini');

say "アプリ名: " . $config->get('app_name');

$config->set('debug', 0);

say "デバッグモード(メイン側): " . ($config->get('debug') ? 'ON' : 'OFF');

my $logger = Logger->new();
$logger->debug("処理を開始します");

say "デバッグログは出力されませんでした";

config.ini

1
2
3
4
# アプリケーション設定
app_name = MyApp
version = 1.0.0
debug = 1

まとめ

  • 前回実装した仕組みは「Singletonパターン」と呼ばれるデザインパターン
  • Singletonパターンは、クラスのインスタンスを1つに保証する
  • GoF(Gang of Four)の23のデザインパターンの1つ
  • 設定管理、ログ出力、DB接続など「1つだけ」が必要な場面で使う
  • 便利だが、テストの難しさなど注意点もある

次のステップへ

Singletonパターンを学んだ皆さんは、他のデザインパターンにも挑戦してみてください。

  • Facadeパターン: 複雑なシステムへのシンプルなインターフェースを提供
  • Adapterパターン: 互換性のないインターフェースを接続
  • Iteratorパターン: コレクションの要素に順番にアクセス

デザインパターンを学ぶことで、より良い設計ができるようになります。ミキさんと一緒に、これからも学び続けましょう!

ミキさんの感想

「デザインパターンって難しそうだと思ってたけど、自然に使えるようになってたんだね。他のパターンも勉強してみたい!」

その意気です!次のシリーズでお会いしましょう。

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。