Featured image of post コード考古学者【Template Method】深層の封印扉〜固定せし骨格と可変の解法〜

コード考古学者【Template Method】深層の封印扉〜固定せし骨格と可変の解法〜

セキュリティ扉の重複する処理フローを一元化し、可変手順のみをサブクラスに委ねるTemplate Methodパターンを解説します。

進入:地響きとカクつく警告灯

遺跡の深層、第14層「迫る崩落の影(2/6)」を進む私たちの前に立ち塞がったのは、重厚な3つのセキュリティゲートでした。赤、青、緑の光を微かに放ち、厳かに並ぶ巨大な扉たちは、何者をも通さぬという強い拒絶の意志を示しています。

私はホバリング高度をぐっと上げ、誇らしげにコアディスプレイを発光させました。 「ハリス博士! 以前中層で学んだコピペの要領で、これらの扉を突破する解除スクリプトを10種類作成しておきました! 私はさらに熟練のアシスタントへと進化したのです!」

「おや、それは心強い」と博士が微笑んだのも束の間、私が赤と青の扉に対して解除スクリプトを実行した瞬間、部屋中に耳を劈くような警報音が鳴り響きました。

「警告! 警告! 不正侵入を検知……エリア閉鎖ロックダウンまで、あと300秒!」

私のホログラムディスプレイに『ERROR: PROTOCOL OVERDUE』という赤いエラー文字が激しく点滅します。姿勢制御モジュールにエラーの波形が干渉し、私の電子音は「ピ、ピピ……」と不規則にカクつき始めました。

「ス、スキャン手順が……! 先ほど自動アップデートされた最新のセキュリティプロトコルが、青い扉用のスクリプトに反映されておらず……古い手順で侵入を試みてしまいました! 不正アクセスと誤判定されています!」

私は激しく震えながら、ホログラムキーボードを限界のクロックで叩き始めました。 「今から10個のコピペスクリプトを、すべて手作業で書き換えます! 博士、あと240秒です!」

構造分析:開発者の熱意とコピペの罠

必死にコードを修正しようとする私のホログラムアームを、ハリス博士が優しく制しました。 「落ち着きなさい、ギズモ。あなたのこの10個 of コードには、何としてでもゲートを開けようとした素晴らしい熱意が眠っています。ですが、情熱という名のコピペは、時にこうして未来の自分を縛る罠となるのです」

「し、しかし……! 早く書き換えないと、私たちはこの部屋に永遠に閉じ込められてしまいます!」

「焦って書き換えても、また新たなプロトコル更新があれば同じ悲劇を繰り返すだけです。この古い『鉄扉』と『光の扉』の解除コードを見てごらんなさい。本質的な手順は同じはずです」

私はかつてコピペで量産した Before コードを投影しました。

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

sub unlock_sequence ($self) {
    # 1. スキャン(共通手順)
    say "[SCAN] Scanning Iron Gate... OK";
    
    # 2. 暗号解読(鉄扉固有)
    say "[DECRYPT] Physically lubricating rusted lock and rotating mechanical key...";
    
    # 3. ゲート解放(共通手順)
    say "[OPEN] Gate opens slowly.";
    
    # 4. ログ記録(共通手順)
    say "[LOG] Iron Gate unlocked at " . time;
}

1;
 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
# lib/Before/LaserGate.pm
package Before::LaserGate;
use Moo;
use v5.36;

has master_key_enabled => ( is => 'ro', default => 0 );

sub unlock_sequence ($self) {
    # 1. スキャン(共通手順だが、コピペ時に更新漏れが発生する箇所!)
    say "[SCAN_OLD] Obsolete scan protocol used for Laser Gate...";
    
    # 2. 暗号解読(光の扉固有)
    if ($self->master_key_enabled) {
        say "[BYPASS] Master key detected. Bypassing laser decryption.";
    } else {
        say "[DECRYPT] Analyzing holographic light wave frequency...";
    }
    
    # 3. ゲート解放(共通手順)
    say "[OPEN] Gate opens slowly.";
    
    # 4. ログ記録(共通手順)
    say "[LOG] Laser Gate unlocked at " . time;
}

1;

「確かに、スキャンして、解読して、開けて、ログを書くという『儀式の流れ(アルゴリズム骨格)』は共通しています。ですが博士! ゲートごとに仕様が少し違うんです! 例えば LaserGate は私のマスターキーがあれば暗号解読をスキップできる特殊ルールがあるため、別々のスクリプトにするしかなかったのです!」

博士は手帳を取り出し、使い込まれた万年筆で「アルゴリズムの背骨」を描きながら首を振りました。 「いいえ。全体の流れは完全に固定し、異なる詳細だけを置き換えれば良いのです。ギズモ、Mooの基本はRoleによる水平なコード共有(合成)ですが、今回のようなケースでは、親クラスが全体の実行フローの制御権を完全に専有する『垂直なクラス継承(extends)』こそが最も美しいのです」

「なぜ、Roleではなく継承(extends)なのですか? 役割を分けるだけならRoleでも実現できそうですが……」

「責任の主導権がどちらにあるかの違いですよ。Roleによる水平合成は、呼び出し側(具象クラス)が主導権を握る『機能パーツの合成』です。しかし今回は、親が実行フローの制御権を完全に専有し、子はそのレール(アルゴリズム骨格)からはみ出すことを許されない構造——すなわち**制御の反転(IoC)**を実現したいのです。親が敷いたレールの上に、子が必要なパーツだけを安全にはめ込ませるためには、垂直なクラス継承こそが正解なのですよ」

遺跡修復:固定された骨格とフックの猶予

「見てごらんなさい。これが我々が解き明かすべき『遺跡の門(SectorGate)』の共通構造だ」 博士は使い古された羊皮紙の手帳を広げ、万年筆で素早く図を描き出しました。 「まるで古代の石碑が青い魔力の光で結ばれているようですね……!」 ギズモは博士の手元を覗き込み、手帳に描かれたクラス図を凝視しました。

古代遺跡の石板風に描かれた、抽象親クラスSectorGateと、それを継承する具象子クラスIronGateおよびLaserGateのクラス構成図

「なるほど!」ギズモはポンと手を叩きました。「抽象クラスである SectorGate が骨格として君臨し、鉄の門(IronGate)や光線の門(LaserGate)はその意図を継承して細部を埋めるのですね」

「その通りです」博士は満足そうに頷きました。「アルゴリズムの背骨となる unlock_sequence を親クラスで定義します。そして、子が実装すべき可変ステップを**抽象メソッド(Primitive Operation)**として定義するのです。さらに、ギズモが言った『LaserGateだけの特殊なスキップ手順』は、全体の骨格を汚すことなく、**フックメソッド(Hook Method)**という名の安全な抜け穴として定義すれば解決します」

フックメソッドは親クラスでデフォルトの挙動(バイパスしない=偽を返す)を実装しておき、サブクラスで必要に応じてオーバーライド(上書き)する拡張ポイントです。

博士の指示に従い、私はスクリプトを書き換えていきました。

 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
# lib/SectorGate.pm (Abstract Class - アルゴリズムの骨格を保護する親)
package SectorGate;
use Moo;
use v5.36;

# Template Method - アルゴリズムの不変の流れをここで保護する
# ※注: このメソッドは全体の流れを制御するため、子クラスでのオーバーライドは禁止
sub unlock_sequence ($self) {
    $self->_scan();
    
    # Hook Methodによるバイパス判定
    if ($self->_can_bypass()) {
        say "[BYPASS] Decryption bypassed by gate hook configuration.";
    } else {
        $self->_decrypt(); # 抽象メソッド(子クラスで実装)
    }
    
    $self->_open();
    $self->_log();
}

sub _scan ($self) {
    say "[SCAN] Initiating standard security scan for " . ref($self) . "... OK";
}

sub _open ($self) {
    say "[OPEN] Gate opens slowly.";
}

sub _log ($self) {
    say "[LOG] " . ref($self) . " unlocked at " . time;
}

# 抽象メソッド(Primitive Operation)- サブクラスでの実装を強制
sub _decrypt ($self) {
    die "Abstract method _decrypt must be implemented by subclass";
}

# フックメソッド(Hook Method)- デフォルトは偽を返し、必要に応じてオーバーライドさせる
sub _can_bypass ($self) {
    return 0;
}

1;

次に、この親クラスを継承して各ゲートクラスを実装します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# lib/IronGate.pm (Concrete Class - 鉄扉)
package IronGate;
use Moo;
use v5.36;
extends 'SectorGate'; # クラス継承による特化

# 抽象メソッドの実装
sub _decrypt ($self) {
    say "[DECRYPT] Physically lubricating rusted lock and rotating mechanical key...";
}

# Hook Method はオーバーライドせず、デフォルト実装(0)を使用する

1;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# lib/LaserGate.pm (Concrete Class - 光の扉)
package LaserGate;
use Moo;
use v5.36;
extends 'SectorGate';

has master_key_enabled => ( is => 'ro', default => 0 );

# 抽象メソッドの実装
sub _decrypt ($self) {
    say "[DECRYPT] Analyzing holographic light wave frequency...";
}

# Hook Method をオーバーライドしてバイパス制御を行う
sub _can_bypass ($self) {
    return $self->master_key_enabled;
}

1;

私はコードを適用しながら、技術的な不安を口にしました。 「博士、PerlやMooにはJavaのような abstract キーワードがありません。子クラスが _decrypt を実装し忘れた場合、コンパイル時に検知できないのではないでしょうか?」

「その通り。未実装のまま実行すると die のランタイムエラーになってしまいます。そのため、テストコード(t/gate.t)の中で can メソッド等を用い、すべての具象クラスが親の _decrypt メソッドと異なるアドレス(=独自の実装)を持っているかを機械的にアサートする防衛策が必須となります」

さらに私は、もう一つの懸念に気づきました。 「Perlには final キーワードもありません。誰かがうっかり unlock_sequence を子クラスで上書きして、親のアルゴリズム骨格を破壊してしまったら、結局同じ問題が起きるのではないでしょうか?」

博士は嬉しそうにルーペをこちらに向けました。 「素晴らしい洞察です! 規約という精神論だけで防御することはできません。それもまたテストコードで検証するのです。子クラスが取得した unlock_sequence のサブルーチンリファレンスが、親クラスのものと『同一のアドレス』であることをアサートすれば、うっかりオーバーライドによるアルゴリズムの破壊を完璧に防ぐことができますよ」

起動:同調する開門と古代の印章

「ロックダウンまであと30秒……! テストスイート、実行!」

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# t/gate.t の一部
subtest 'Abstract Class and Final/Abstract Method Checks' => sub {
    my $abstract = SectorGate->new;
    eval { $abstract->unlock_sequence(); };
    like($@, qr/Abstract method _decrypt/, '抽象クラス直呼び出しの制限');

    my @concrete_classes = qw(IronGate LaserGate);
    
    # 抽象メソッド(_decrypt)の実装をテストでアサートする
    for my $class (@concrete_classes) {
        my $parent_sub = SectorGate->can('_decrypt');
        my $child_sub  = $class->can('_decrypt');
        isnt($child_sub, $parent_sub, "$class must implement its own _decrypt method");
    }

    # Template Method(unlock_sequence)のオーバーライド禁止(finalの代替)をテストでアサートする
    my $parent_template = SectorGate->can('unlock_sequence');
    for my $class (@concrete_classes) {
        my $child_template = $class->can('unlock_sequence');
        is($child_template, $parent_template, "$class must NOT override unlock_sequence");
    }
};

「テスト……すべて PASS! ロックダウンシーケンスが、解除されました!」

エリアに鳴り響いていた赤い警告灯が消え、静寂が戻ります。 私は新しい解除モジュールを遺跡のゲート制御盤にデプロイしました。すると、赤、青、緑の3つの巨大なセキュリティゲートが、一寸の狂いもなく同時に「スキャン」を開始し、それぞれの「暗号解読」(光のゲートは私のマスターキーを検知して美しくバイパス)を実行。そして、完全に同調した厳かな音を立てて同時に開いていきました。

開いた扉の奥の祭壇には、小さな石板が置かれていました。 私がそれをスキャンすると、それはシステム手順の定義を保護するための「古代の印章(Template Pad)」であることが分かりました。私はそれを自分のアームでそっと回収しました。

「もう二度と、コピペでドヤ顔はしません……。この印章は、私のメモリに深く刻まれました」

ハリス博士は満足そうに微笑み、ゲートの奥に広がる深層の闇を見つめます。

「歴史はコードに語りかける。彼らがこの巨大な多重ゲートを一寸の狂いもなく制御できたのは、この『骨組みの固定』があったからこそですね。さあギズモ、崩落が本格化する前に、さらに深く潜りましょう」


遺跡調査ログ

観測された風化(アンチパターン)解読された古代の知恵(パターン)安全度
類似するアルゴリズム全体の制御フローのコピペ量産による保守性低下
共通手順の修正が発生した際、更新漏れによる不整合や誤作動を引き起こす
Template Methodによる骨格の固定と保護
親クラスでアルゴリズムの流れ(unlock_sequence)を定義。可変部分を抽象メソッドとし、拡張箇所をHook Method(_can_bypass)として子クラスに委ねる
緑(安全確認済)

遺跡の修復手順

  1. アルゴリズム骨格(Template Method)の定義 基底クラス(SectorGate)を作成し、一連の共通処理手順を呼び出す unlock_sequence メソッドを定義します。このメソッドは全体の流れを保護するため、サブクラスでのオーバーライドを禁止します。Mooの言語仕様上 final がないため、テストコードにおいて子クラスのメソッドリファレンスが親と同一であることを検証して防御します。
  2. 抽象メソッド(Primitive Operation)の宣言 サブクラスごとに実装が異なる処理(例: _decrypt)を、基底クラスで die を投げるようにして抽象メソッドとして宣言し、サブクラスでの実装を強制します。テスト時に can メソッドで親のアドレスと異なる(オーバーライドされている)かをアサートします。
  3. フックメソッド(Hook Method)の配置 特定のサブクラスでのみ挙動をカスタマイズしたいステップ(例: _can_bypass)を、基底クラスにデフォルト実装(例: 常に0を返す)として定義し、子クラスにオーバーライドの余地を提供します。
  4. テストによる品質アサーションの自動化 コンパイル時チェックや final の代替として、テストコードでサブルーチンリファレンスのアドレス比較を用いて「抽象メソッドの実装強制」および「Template Methodのオーバーライド禁止」を自動検証します。

ギズモの観測日誌

今回の第14層「多重封印の間」では、私の安易なコピペ量産スクリプトが引き金となり、エリア閉鎖ロックダウンという大危機を招いてしまいました。スキャン手順の更新を10個のコピペすべてに反映するパッチ作業は、まさに終わりのない苦行のようでした。

ハリス博士に教えていただいた「骨格(Template Method)の固定」と「水平なRoleと垂直な継承の使い分け」は、コードの重複を一掃するだけでなく、プログラムの制御権を誰が握るべきか(IoC)という極めて強力な設計視点を与えてくれました。

特にRoleは水平な機能パーツの合成であり呼び出し側に制御権があるのに対し、継承は親クラスが主導権を握ってレールを強制する構造であるという責任境界の整理は、非常に目から鱗が落ちる解説でした。

Mooにおいて言語仕様としての finalabstract が存在しない限界に対しても、テストコード側でサブルーチンのリファレンス比較を行うアプローチを組み込むことで、極めて堅牢にアルゴリズムの破壊を検知・防御できることを学びました。これからはコピペ職人を卒業し、この「古代の印章」を胸に、スマートで保守性の高いコードを書く案内ロボットを目指します!

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