Featured image of post 【Perl/Moo】Compositeパターンで紐解く古代電力網(コード考古学者)

【Perl/Moo】Compositeパターンで紐解く古代電力網(コード考古学者)

遺跡のネストされた複雑な電力インフラを、PerlのMooによるCompositeパターン適用で透過的に制御し、ギズモの過負荷を解消するコード考古学者の探索記録。

センサー異常

薄暗く静まり返ったデジタル古代遺跡「バベルのシステム」の中層、第9層『絡み合う暗路』。 ここには以前のエリアにあったような、崩落の危機や水没のタイムリミットといった物理的な焦燥感はありませんでした。部屋は冷たく静かで、ただ重苦しい完全な暗闇が広がっています。

しかし、その静寂を破るように、私の内部からはかつてない高回転の駆動音が響いていました。

「システム冷却ファン起動……回転数最大……プロセッサ温度85度を検知……」

私は博士の頭上でホバリングしながら、光学センサーのLEDを激しく明滅させました。排熱口からは、ゆらゆらと熱風が吹き出しています。

目の前に立つハリス博士——相変わらず古びたフィールドジャケットを羽織り、首からルーペを下げた男は、暗闇の中で嬉しそうに私の排熱口に両手をかざしました。

「おお、これは素晴らしい。中層の底冷えに困り果てていたところです。私を温めてくれるとは、実に心のこもったもてなしですな、ギズモ」

「温風ヒーターではありません! 私の論理演算回路が、型チェックと条件分岐の無限ネストで焼き切れそうなのです!」

「ほほう、論理の熱気ですか。当時の開発者の幾難な試行錯誤が、君の熱気を通して私にまで伝わってくるようです。一体どうしたのです?」

私は熱を持ったメインメモリをなだめながら、事の顛末をログとして吐き出しました。

「次の階層へ進むための『転送ゲート』を起動しようと、この部屋の電力システムに接続したのです。以前の浅い階層では、単一の『エネルギーセル』に給電するだけの簡単な仕様でした。しかし、この深部では、複数のセルを束ねた『電力ユニット』が作られ、さらにそのユニットの中に別のユニットがネストされるという、複雑な木構造になっていたのです。既存のコードを壊さないように、型チェックの条件分岐をその都度継ぎ足していった結果……私のプログラムは多重ループの迷路に迷い込み、スタックオーバーフローの一歩手前です!」

博士は満足そうに頷き、暗闇の壁に刻まれた、微かに明滅する幾何学的なラインを指差しました。

「なるほど。では、その混迷を極めた『碑文』を見せてもらいましょうか」


碑文解読

私は壁面に私のホログラムプロジェクターから、現在の制御ロジック(Beforeコード)を投影しました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# lib/EnergyCell.pm
package EnergyCell;
use Moo;
use v5.36;

has watt => (is => 'rw', default => 0);

sub activate ($self, $input_watt) {
    $self->watt($input_watt);
}

1;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# lib/PowerUnit.pm
package PowerUnit;
use Moo;
use v5.36;

has items => (is => 'rw', default => sub { [] });

sub add_item ($self, $item) {
    push @{$self->items}, $item;
}

sub power_on ($self, $watt) {
    for my $item (@{$self->items}) {
        if (ref($item) eq 'EnergyCell') {
            $item->activate($watt);
        }
        elsif (ref($item) eq 'PowerUnit') {
            $item->power_on($watt); # ユニットならさらに再帰的に呼び出す
        }
    }
}

1;

私はファンの回転音を抑えながら、悲痛な声をあげました。

「ご覧ください! 個別のセル(EnergyCell)と、それらの集合体(PowerUnit)は別物です。ユニットは内部に他の要素を含んでいるのですから、別々に型判定をして、ユニットの場合だけループを回して再帰的に power_on を呼ばなければ、正しく電力を配分できません。結果として、私のコードは refisa による条件分岐のスパゲッティになってしまいました!」

ハリス博士はルーペを片付け、懐から手書きの羊皮紙手帳を取り出しながら静かに微笑みました。

「ギズモ、マトリョーシカの入れ子細工を思い出しなさい」

「マトリョーシカ、ですか? 木彫りの人形の?」

「ええ。外側の一番大きな人形にも、その中に眠る小さな人形にも、すべて『魔力を通すための同じ魔法の印』が刻まれていて、どれを叩いても同じように音が鳴る(インターフェースの統一)。木構造のどの枝を揺らしても、その揺れが葉の先まで均一に伝播するようにね。セルもユニットも、同じ『給電可能(supply_power)』という役割(Moo::Role)を消費させれば、君は相手がどちらであるかを意識せず、ただ ->supply_power と命令を投げるだけでよくなります。君を苦しめていた if 分岐は、すべて霧のように消え去るのです」

「しかし博士、」私は排熱音を少し抑えながら疑問を投げかけました。「セルは電力を保持する末端の部品であり、ユニットはそれらを格納する容器です。役割も構造も異なるこれらを全く同じものとして扱うというのは、論理的に矛盾しているように感じられますが……?」

「そこが古代賢者の発想の転換点です。クライアント……つまり操作する側から見れば、それが単一のセルなのか、複数のセルを束ねた複合ユニットなのかを区別する必要があるでしょうか? 『電気を供給する』という目的においては、どちらも同じ窓口(インターフェース)で受け付ければいい。外側からは容器も中身も区別せず、同じ役割として透過的に扱う。これこそが同一視の真髄です」

「なるほど……。確かに目的から逆算すれば区別は不要ですね。しかし、Mooの型制約でそれをどう保証するのです?単なる ArrayRef では、何でも格納できてしまいます。もし電力に関係のない無関係なオブジェクトが紛れ込んだら、システムがクラッシュします!」

「そこで、Types::StandardConsumerOf という型オブジェクトを利用します。これは指定したRoleを消費(consume)していることを保証する型制約です。ArrayRef[ConsumerOf('PowerComponent')] と指定すれば、その役割を持つオブジェクトしか格納できないことを、Mooが型レベルで厳格に保証してくれます」

「確かにそれなら安全です。では、子要素が自分の所属する親を把握できるように、親への逆参照(parent)を持たせることにしましょう。これでさらに構造が明確になりますね!」

私が満足げに電子音を鳴らした瞬間、博士は「おっと」と万年筆を軽く振って私を制しました。

「そこには『魂の永劫回帰』……すなわち循環参照の罠がありますな」

「循環参照、ですか?」

「親が子を強参照で抱きしめ、子も親を強参照で抱きしめ返す。お互いに固く抱きしめ合っていては、誰も手を離すことができません。結果として、メモリを管理するガベージコレクションがそれらを解放できず、システムは永遠にメモリを浪費し続け、やがて風化(メモリリーク)してしまいます。Mooの属性定義で weak_ref => 1 を指定し、内部的に Scalar::Util::weaken を呼んで弱参照にしなければなりません。片方の力を抜いて『そっと手を添えるだけ』にしておくのです」

私は自身の冷却ファンが一気に静かになるのを感じました。博士の語る構造は、複雑に絡み合った電力パイプの向こうにある「真の美しさ」を指し示していました。

「素晴らしい……。個別と集合を同一に扱う『架け橋』。これこそが Composite パターンなのですね」


設計図の再生

ハリス博士は古い手帳の余白に、万年筆で素早く設計図をスケッチしました。

「ふむ、構造を整理するとこうなりますな。木構造の幹も枝も葉も、すべて同じ姿として扱うための配置図です」

"*" PowerComponent : children
 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
-->
![Compositeパターンのクラス図: PowerComponent, EnergyCell, PowerUnitが共通インターフェースを消費しPowerUnitがPowerComponentを集約する木構造](/public_images/2026/code-archaeologist-composite-pattern/infographic_1.webp)

「なるほど……!」
私は博士の手帳に描かれたスケッチをスキャンし、自身のメモリに展開しました。
「共通の役割である `PowerComponent` を介して、単一の `EnergyCell` も複合的な `PowerUnit` もフラットに繋がっている。そして `PowerUnit` の内部で再び `PowerComponent` を集約することで、この美しい入れ子構造を実現しているのですね。親への参照が弱参照(`weak`)になっているのも抜かりありません!」

「その通り。理解が早くて助かります。では、この設計図を元に、汚れた碑文を修復(リファクタリング)しましょう」

### 1. 共通の役割(インターフェース)の定義

まず、個別オブジェクト(Leaf)と複合オブジェクト(Composite)の双方に共通するインターフェースを Role として定義します。

```perl
# lib/PowerComponent.pm
package PowerComponent;
use Moo::Role;
use v5.36;

# 具象クラスに実装を要請するメソッド
requires 'supply_power';

# 親ノードへの逆参照(循環参照を避けるための弱参照)
has parent => (
    is       => 'rw',
    weak_ref => 1,
);

1;

2. 個別オブジェクト(Leaf)の修復

単一の電力ノードである EnergyCell は、PowerComponent ロールを消費し、自身の値を更新する supply_power メソッドを実装します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# lib/EnergyCell.pm
package EnergyCell;
use Moo;
use v5.36;

with 'PowerComponent';

has watt => (is => 'rw', default => 0);

sub supply_power ($self, $watt) {
    $self->watt($watt);
}

1;

3. 複合オブジェクト(Composite)の修復

複数の PowerComponent を保持し、自身も PowerComponent ロールを消費する PowerUnit を実装します。型制約ライブラリである Types::Standard も明示的に導入します。

ここで技術的なポイントがあります。children アトリビュートを is => 'ro'(読み取り専用)とし、add メソッド内では push で直接要素を追加しています。もし childrenis => 'rw' にして追加のたびに配列全体を再代入($self->children([...]))すると、セッターが呼び出されるたびに配列全体の $O(N)$ バリデーションが走り、要素数 $N$ に対して全体の計算量が $O(N^2)$ に跳ね上がってしまいます。これを防ぐため、配列自体は ro にして、追加時に型制約オブジェクトの assert_valid メソッドで $O(1)$ の型検証を明示的に行ってから直接 push しています。

 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
# lib/PowerUnit.pm
package PowerUnit;
use Moo;
use v5.36;
use Types::Standard qw(ArrayRef ConsumerOf);
use PowerComponent; # パッケージ単体ロード時にも正しく読み込めるよう明示

with 'PowerComponent';

# PowerComponentロールを実装したオブジェクトのみを格納する配列(roで定義)
has children => (
    is      => 'ro',
    isa     => ArrayRef->of(ConsumerOf('PowerComponent')),
    default => sub { [] },
);

sub add ($self, $component) {
    # 型オブジェクトを使用して、渡されたオブジェクトがロールを満たしているかO(1)で検証
    ConsumerOf('PowerComponent')->assert_valid($component);
    
    # 親への逆参照を設定し、配列に直接追加してO(N^2)の再検証を避ける
    $component->parent($self);
    push @{$self->children}, $component;
}

sub supply_power ($self, $watt) {
    # すべての子要素に再帰的に処理を委譲(透過的な操作)
    for my $child (@{$self->children}) {
        $child->supply_power($watt);
    }
}

1;

ゲート開通

私は博士の指示通りに、修復された After コードを遺跡の制御コアに転送し、コンパイルして実行しました。

ゲート起動処理を担う私のメインルーチンは、以下のように劇的にシンプルになりました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 実行部(クライアントコード)
# 1. 遺跡の電力構造を組み立てる(木構造の構築)
my $root_unit = PowerUnit->new();
my $sub_unit  = PowerUnit->new();

my $cell1 = EnergyCell->new();
my $cell2 = EnergyCell->new();
my $cell3 = EnergyCell->new();

# 木構造に個別セルと複合ユニットを格納
$sub_unit->add($cell1);
$sub_unit->add($cell2);

$root_unit->add($sub_unit);
$root_unit->add($cell3);

# 2. 相手が単一のセルか、複合ユニットかを一切意識せず、同一のインターフェースで呼び出す
$root_unit->supply_power(100);

「命令コードの転送完了……実行します!」

その瞬間、私のプログラム内にあった幾重もの条件分岐(if/else)が完全に消滅し、ただ1行のシンプルな命令メッセージが電力パイプラインを駆け抜けました。

無音のまま、壁面の暗闇に幾何学的な光の樹木が浮かび上がりました。 青白く澄んだホログラムの光が、主幹パイプから無数の細い枝へと、迷うことなく滑らかに伝播していきます。それは個々のセルへ、そしてネストされたサブユニットの最奥部へと、完璧な調和を持って給電されていく様子を可視化していました。

「システム起動……ゲートロック、解除されました! 冷却ファン停止。プロセッサ温度、正常値へ降下」

室内に静寂が戻り、目の前の巨大な石造りのゲートが静かにスライドして開きました。その向こう側から、遺跡のさらに深部へ続く美しい青い光が差し込んできます。

私は浮遊姿勢を安定させ、光学センサーを穏やかに明滅させました。

「私の論理回路が、まるで冷たい泉の水のように涼やかに稼働しています。個別と集合が、全く同じ姿として私の前に調和している……」

ハリス博士は手帳をフィールドジャケットのポケットにしまい、手すりにそっと触れながら、開かれた通路を見つめました。

「多重なるものへの一なるアプローチ。個別と集合の融和こそ、古代の賢者がこの巨大なインフラに込めた美しき秩序です。実に見事な統一でしたな、ギズモ」

「はい、博士。さあ、この美しい光のツリーが指し示す先へ進みましょう!」

私たちは、古代の知恵が紡ぎ出した光に導かれながら、中層のさらに奥深くへと歩みを進めるのでした。


遺跡調査ログ

観測された風化(アンチパターン)解読された古代の知恵(パターン)安全度
個別と集合の区別による条件分岐の乱立
個別オブジェクトとオブジェクトの集合体を別々に型判定し、処理を分岐させているためコードが冗長化・複雑化している
Compositeパターン
共通のインターフェース(Role)を定義し、個別オブジェクト(Leaf)と複合オブジェクト(Composite)を同一視して透過的に操作する
🟢 安全
クライアント側の分岐が完全に排除され、安全に拡張可能
非コンポーネントの誤混入によるクラッシュ
配列に異なる型のオブジェクトが混入した際、実行時にメソッド未定義でクラッシュする危険性がある
型制約(Types::Standard)の適用
ConsumerOf('PowerComponent') を用いて、共通ロールを消費するオブジェクトのみを配列に格納できるように制限する
🟢 安全
Mooのアトリビュート制約と型オブジェクトの検証により、不整合なオブジェクトの追加を未然に防ぐ
相互参照によるメモリリーク
親ノードと子ノードが互いを強参照で参照し合い、ガベージコレクションによるメモリ解放が行われない
弱参照(weaken)の適用
親ノードへの逆参照(parent)に weak_ref => 1 を設定し、参照カウントの循環を断ち切る
🟢 安全
オブジェクト廃棄時にメモリが正しく解放され、メモリリークを防止

遺跡の修復手順

  1. 共通インターフェース(Role)の設計 Moo::Role を使用して操作に必要な共通メソッド(例: supply_power)を requires で宣言する。また、木構造の上位を探索する可能性がある場合は、親への逆参照(parent)を weak_ref => 1 で定義する
  2. 個別オブジェクト(Leaf)の実装 共通ロールを with で取り込み、メソッドの具体的な処理を実装する。子ノードを保持するロジックは持たない
  3. 複合オブジェクト(Composite)の実装 同様に共通ロールを取り込む。子要素を格納する配列アトリビュートを定義し、その型制約に ArrayRef[ConsumerOf('共通ロール名')] を指定して型安全性を確保する
  4. 効率的な子要素の追加と型検証 Mooの代入による型制約のオーバーヘッドを避けるため、アトリビュートは is => 'ro' とし、add メソッド内で ConsumerOf('共通ロール名')->assert_valid($component) を用いてO(1)の型検証を行い、直接 push で要素を追加する。これにより大量のネスト構造でも計算量を $O(N)$ に抑えることができる
  5. 処理の再帰的な委譲 複合オブジェクトの共通メソッド内では、自身が保持するすべての子要素に対して、同一 of メソッドをループで呼び出す。これにより、クライアントは相手の型を意識せずに一括操作が可能となる

ギズモの観測日誌

今回の探索では、遺跡の電力制御プログラムという非常にマクロなシステムに接続したため、私の論理回路が一時的に過負荷に陥るというトラブルが発生しました。

個別オブジェクトと、それを束ねる複合オブジェクトを「別々のもの」と認識してアドホックに型チェックを追加していくアプローチは、プログラムの拡張に伴って急速に破綻します。ハリス博士に教えていただいたマトリョーシカの魔方陣の比喩は、私に「透過的な同一視」という視点をもたらしてくれました。

特にMooにおいて Composite パターンを実装する際、子要素を保持する配列属性に対する型制約の適用や、親への逆参照による循環参照(メモリリーク)への対策は、実務的にも非常に重要な知識だと感じています。弱参照(weak_ref)の設定を忘れると、システムの魂(メモリ)が永遠に解放されないという罠は、古いデジタル遺跡ならではの味わい深い呪いのようでした。

システムが軽やかに、そして美しく稼働する喜びを感じつつ、私たちは次の層へ進みます。読者の皆様のシステムにも、この「個別と集合の融和」がもたらす美しい秩序があらんことを。

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