Featured image of post 第4回-インスタンスを1つにしよう — 設定ファイルマネージャーを作ってみよう

第4回-インスタンスを1つにしよう — 設定ファイルマネージャーを作ってみよう

Perl/Mooでinstance()メソッドを実装し、インスタンスを1つに統一する方法を解説。クラス変数による単一インスタンス保証の仕組みを学びます。

@nqounetです。

前回は、複数の場所から設定を使おうとして、設定が反映されない問題に遭遇しました。

今回は、インスタンスを1つに統一することで、この問題を解決します。

今回のゴール

クラス変数とinstance()メソッドを使って、Configのインスタンスを1つだけに保つ仕組みを作ることです。

アイデア

前回の問題を振り返ってみましょう。

	flowchart TB
    subgraph 問題
        A[new するたびに] --> B[別のインスタンスが作られる]
        B --> C[設定がバラバラになる]
    end

解決策は単純です。インスタンスを1つだけにして、みんなで共有すればいいのです。

	flowchart TB
    subgraph 解決
        D[1つのインスタンスを作る] --> E[みんなで共有する]
        E --> F[設定が統一される]
    end

クラス変数でインスタンスを保持する

Perlでは、state変数を使ってクラス全体で共有する値を保持できます。

1
2
3
4
5
6
7
sub instance ($class) {
    state $instance;
    if (!$instance) {
        $instance = $class->new();
    }
    return $instance;
}

ポイントを見てみましょう。

state変数

state $instance;は、サブルーチンの呼び出しをまたいで値を保持する変数です。一度値を設定すると、次回呼び出しても同じ値が残っています。

インスタンスの作成と再利用

1
2
3
4
if (!$instance) {
    $instance = $class->new();
}
return $instance;
  • 初回呼び出し: $instanceは未定義なので、newでインスタンスを作成
  • 2回目以降: すでにインスタンスがあるので、それをそのまま返す

instance()メソッドの実装

Configクラスにinstance()メソッドを追加しましょう。

 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 { {} });

    # クラスメソッド: 単一インスタンスを返す
    sub instance ($class) {
        state $instance;
        if (!$instance) {
            $instance = $class->new();
        }
        return $instance;
    }

    # 以下、既存のメソッド...
};

使い方の変更

これまではnewでインスタンスを作っていましたが、今後はinstance()を使います。

1
2
3
4
5
# 変更前
my $config = Config->new();

# 変更後
my $config = Config->instance();

どこで呼び出しても、同じインスタンスが返ってきます。

Loggerモジュールの修正

Loggerモジュールもinstance()を使うように修正します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package Logger {
    use Moo;
    use v5.36;

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

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

load_configは、メインスクリプトで一度だけ呼べばOKです。

完成したコード

動作を確認してみましょう。

 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
70
71
use v5.36;
use Moo;

package Config {
    use Moo;

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

    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;

# instance() でConfigを取得し、設定を読み込む
my $config = Config->instance();
$config->load_config('config.ini');

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

# デバッグモードをOFFに変更
$config->set('debug', 0);

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

# Loggerでデバッグログを出力(OFFなので出力されないはず)
my $logger = Logger->new();
$logger->debug("処理を開始します");

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

実行結果

1
2
3
アプリ名: MyApp
デバッグモード(メイン側): OFF
デバッグログは出力されませんでした

設定の変更が、Loggerにも正しく反映されました!

動作の仕組み

図で確認してみましょう。

	flowchart TB
    subgraph Config
        I[instance] --> S[state $instance]
    end

    A[メイン: Config->instance] --> I
    I --> B[同じインスタンス]

    C[Logger: Config->instance] --> I
    I --> B

    B --> D[debug = 0]

instance()を使うことで、メインスクリプトもLoggerも、同じConfigインスタンスを参照します。だから、設定の変更がどこからでも見えるようになったのです。

第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
use v5.36;
use Moo;

package Config {
    use Moo;

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

    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

まとめ

  • state変数を使って、クラス全体で共有するインスタンスを保持した
  • instance()メソッドで、常に同じインスタンスを返すようにした
  • newの代わりにinstance()を使うことで、設定が統一された
  • どこから設定を変更しても、全体に反映されるようになった

次回予告

ミキさんが喜んでいます。

「これで設定管理がうまくいくようになった!でも、この仕組みって何か名前があるの?」

次回は、今回実装した仕組みが「デザインパターン」と呼ばれるものの1つであることを解説します。

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