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