@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つであることを解説します。