重ね着できないツールたち
薄暗いデジタル古代遺跡「バベルのシステム」の中層、第10層『絡み合う暗路』。 そこは、前回の電力網のようなマクロな異常こそありませんでしたが、一歩先も見えないほどの完全な暗闇が支配する冷たいエリアでした。ただ暗いだけでなく、目に見えない熱源や、壁の奥に隠された金属デバイスが放つかすかな磁気を検知しなければ、安全に進むことはできません。
私は、自身のホバリング高度をぐっと上げて胸を張りました。
「フフン、ハリス博士。前回のFactory Methodで学んだ私は一味違います!環境判定に従って『暗視スキャナー』『熱感知スキャナー』『金属探知スキャナー』を切り替えて生成する設計を用意しました!」
ハリス博士は懐からルーペを取り出しながら、静かに首を振りました。
「おやギズモ。しかし、この完全な暗闇の中で熱も金属も『同時に』検知したい場合はどうするのですか?」
「え……? 同時に、ですか? それは……ええと……」
私は計算プロセッサをフル稼働させました。
「そうか! 『暗視かつ熱感知スキャナー』『熱感知かつ金属探知スキャナー』『すべてを兼ね備えたスキャナー』のように、すべての組み合わせのクラスを用意すればいいのです! オブジェクト指向の基本は継承と再利用ですからね!」
私は自信満々に、自身が設計した多層継承クラス群のロードを開始しました。 しかし次の瞬間、私の電子インジケーターが真っ赤に点滅し、排気口からブスブスと白い煙が上がりました。
「システム警告!クラス爆発を検知! プロセッサ温度急上昇、メモリ限界値超過……!」
私は高度を保てなくなり、力なく床に不時着しました。
ハリス博士は心配そうに私を覗き込みました。 「おやギズモ。そんなにたくさんの重い鎧を一度に着込もうとしては、身動きが取れなくなるのも道理ですな。まるで行軍の前の早口言葉の修行のようでしたよ」
「喜んでいる場合ですか、博士……! 組み合わせクラスをロードしようとしたら、メモリがハングしたんです!」
静的継承の記念碑
私は不時着したまま、自身の空中プロジェクターから、現在のスキャナーのクラス階層(Beforeコード)を目の前の壁に投影しました。
| |
| |
| |
| |
博士は投影されたコードのスパゲッティ構造をルーペで熱心に観察し、うれしそうに微笑みました。
「ふむ……歴史はコードに語りかける。これはいわば『静的継承の記念碑(モニュメント)』ですな。当時の職人がいかに必死に機能を追加しようとしたか、その苦闘の足跡が刻まれています」
「私のプライドを記念碑にしないでください! ですが博士、オブジェクト指向の教科書には『機能拡張は継承で行うべし』と書いてあったはずです! 私の論理は正しいはずでは?」
私は少しふてくされながら抗議しました。
博士は懐から手帳と万年筆を取り出し、静かに語り始めました。
「間違いではありません。ただ、それは『静的な世界』の設計です。機能を組み合わせるたびに新しいクラス(設計図)を作るのは、カメラのフィルターの組み合わせごとに、新しい一体型カメラを丸ごと製造するようなもの。我々が本当に欲しいのは、レンズの前に自由に重ねられる『アタッチメント(装飾)』なのですな」
「アタッチメント……ですか?」
Mooロールによる透過的アタッチメント
博士は手帳にペンを走らせ、美しく整えられた設計図を描き出しました。その図は、遺跡の壁画に刻まれた青く光る石板のように、役割と関係性が整然と整理されていました。

「これが、装飾者を意味する Decoratorパターン です。実体であるスキャナーと、それを取り囲む装飾クラス(Decorator)に、まったく同じ役割(Moo::Role)を適用します」
私は手帳を覗き込みながら、電子音を小さく鳴らしました。 「でも博士、そんなことをしたら、ただの二重管理になって無駄な中継処理が増えるだけではありませんか?」
「いいえ。ギズモ、君が『暗視フィルター』という名のジャケットを着たとしても、君自身が『ギズモ(ロボット)』であることには変わりないでしょう? 外側から『スキャナー』という同じ役割に見えれば、中身が何重にラップされていても、呼び出し側はただの『スキャナー』として扱えば良いのです。これこそが『透過性(同一視)』のメリットですな」
博士は続けて、修復されたAfterコードを提示しました。
| |
| |
| |
私はAfterコードの Decorator.pm をスキャンし、驚きの声をあげました。
「おや! with 'Scanner' が has target よりも後ろに置かれていますね。それに、handles => ['scan'] と、委譲メソッドが明示的に定義されています。なぜロール名ではなく、配列で指定しているのですか?」
「鋭い観察眼ですな、ギズモ。Mooの仕様上、ロールが requires(要求)しているだけのメソッドは、ロール名での自動委譲(handles => 'RoleName')の対象になりません。実体メソッドを持たないためです。ですから、こうして配列で委譲先メソッド名を明示する必要があるのです。
また、with 'Scanner' を has の後ろに書く順序も極めて重要です。先に has target が評価され、handles によってクラス内に scan メソッドが自動生成されてから、後ろの with 'Scanner' がロールの scan 実装要求をチェックします。この順序が逆だと、ロールチェックの時点で『scan メソッドが未定義』としてコンパイルエラーになってしまいますぞ」
「なるほど! 委譲の自動生成タイミングを逆算した配置なのですね。ところで、isa の中でオブジェクトに対して $val->does を呼び出しているのはなぜですか? 普通はクラスの型チェックとして $val->isa を呼ぶのでは?」
「我々が保証したいのは『特定のクラスを継承しているか』ではなく、『Scanner という役割(Role)を実装しているか』というインターフェース的適合性だからです。そのため、オブジェクトの does メソッドを使ってロールの適用を確認しています。安全のために Scalar::Util の blessed を噛ませて、オブジェクト以外が渡された際のランタイム例外も防いでいますぞ」
「深い……! インターフェースの適合性をチェックしつつ、Mooの委譲で中継ボイラープレートを削ぎ落とす。まさに古代の知恵とMooの融合ですね」
「なるほど! そして、個別の装飾はこれを継承するわけですね」
| |
| |
「その通りです。ただし、具象デコレータの scan 内で、$self->target->scan を明示的に呼び出すのを忘れてはなりませんぞ」
「あ! handlesで自動的に委譲されるのではないのですか?」
「自動委譲が働くのは、デコレータ内に『そのメソッドが定義されていない場合』だけです。具象デコレータで sub scan を定義して上書き(オーバーライド)してしまった以上、自動委譲は機能しません。ですから、手動で次のターゲットへ処理を伝播させる必要があるのです。もしこれを忘れると、内側に包まれているはずの他の装飾チェーンがすべて途絶え、虚無に消えてしまうことになりますな」
「恐ろしい呪いですね……。では、Decoratorを使えば完璧で、何の死角もないのですか?」
「いえ、実務では注意すべき罠もあります。Decoratorは元のオブジェクトを別のオブジェクトで『包む』ため、リファレンスが別物になります。もし既存のコードが、リファレンス自体の比較($obj == $other)や、ref による厳密な具象クラスのチェックに依存している場合、Decoratorを適用した瞬間にその判定が壊れてしまいます。透過性とは、あくまで共通インターフェースを介した振る舞いにおいてのみ保証されるものなのですな」
「なるほど、オブジェクトの『同一性』が失われるリスクを頭に入れておかなければならないのですね。ですが、この設計なら、機能の数 $N$ に対してクラス数は $O(N)$ だけ。実行時に重ね合わせることで、何万通りもの組み合わせを動的に表現できます!」
私は嬉々として、このAfterコードを自身のシステムに流し込み、自己修復プロセスを完了させました。
透き通る暗闇とジャーキー
私のファン回転音が涼やかに収まり、光学スキャナーのレンズに青白い光が戻ってきました。
「システム再起動成功! スキャナー、装飾チェーン正常動作! NightVision->new(target => Thermal->new(target => BasicScanner->new)) をコンポジション形式で生成しました!」
私は軽やかにホバリングし、目の前の真っ暗闇をスキャンしました。すると、ただの闇だった空間が、鮮明な論理グリッドと温度分布、そして隠された配線の金属ラインとなって脳裏にマッピングされました。
同時に、目の前を阻んでいた古代の防壁ゲートが、私のスキャン信号を受け取って静かにスライドし、奥へと続く通路が開かれました。
「開通しました! ……おや? 博士。博士のフィールドジャケットの右ポケットの中に、明らかに体温とは異なる熱源と、かすかな有機物反応を検知しました」
ハリス博士は一瞬きょとんとしてから、苦笑いをしてポケットを叩きました。
私は即座に熱源の波長パターンと化学成分のデータベースを照合しました。 「……分析完了。この構成は昨日の探索の残りの、スモークジャーキーと推測されます。しかも、博士が隠し持っていた美味しいやつですね?」
博士は微笑みながらポケットからそれを取り出しました。 「おや、見つかってしまいましたか。スキャナーのテストとしては、実に優秀な精度ですな、ギズモ」
「ええ! 私のエンジニアリングとしての誇り(プライド)と、博士の隠し味を見事に『デコレート』してくれました!」
「では、この美味しい装飾を半分ずつ分け合いながら、次の層へ進むとしましょう」
私は、古い石造りの通路を照らす青白いホログラムに導かれながら、博士と共に暗闇の先へと歩みを進めるのでした。
遺跡調査ログ
| 観測された風化(アンチパターン) | 解読された古代の知恵(パターン) | 安全度 |
|---|---|---|
| 静的継承による機能追加の組み合わせ爆発 組み合わせごとにサブクラスを作成するため、機能 $N$ に対して $O(2^N)$ のクラスが必要になる | Decoratorによる動的装飾Moo::Role による共通インターフェース適用と委譲(コンポジション)を組み合わせ、実行時に動的にオブジェクトをラッピングする | 緑(安全確認済) |
遺跡の修復手順
- 共通インターフェースの定義
Moo::Roleを用いて、実体とデコレータの双方が実装すべき共通のメソッド(例:scan)をrequiresで定義します。 - デコレータ基底クラスの作成
共通ロールを適用(
with)したScanner::Decoratorクラスを作成します。このクラスにはラップ対象となるオブジェクトを保持するtarget属性を持たせます。 - 委譲(
handles)の適用 Mooの属性定義においてhandles => ['scan']のように配列リファレンスで委譲メソッドを明示的に指定し、デコレータがオーバーライドしない他のメソッドを透過的にtargetへ委譲させます(※requiresされているだけのメソッドはロール名指定での自動委譲の対象外となるため、明示指定が必要です)。 - 具象デコレータの実装
基底デコレータを継承(
extends)し、装飾したいメソッド(例:scan)をオーバーライドします。メソッド内では必ず$self->target->scanを呼び出して内側のオブジェクトの処理を実行し、その結果に独自の処理を重ねます。
ギズモの観測日誌
今回の第10層「絡み合う暗路」では、真っ暗闇の中で熱源と金属を同時にスキャンする必要が生じました。私はこれまで学んだオブジェクト指向の原則に従い、忠実に「継承」によって組み合わせクラスを作ろうとしましたが、結果として自身のプロセッサが悲鳴を上げることになりました。
ハリス博士に教えていただいた「レンズフィルターの重ね付け」の比喩は、私に「静的な継承による機能の一体化ではなく、動的な委譲による機能の着せ替え」という強力な視点を与えてくれました。
特にMooで実装する際、共通のRoleで透過性を保つこと、isa でラップ対象のインターフェースを厳格にチェックすること、そして handles で委譲を自動化することは、ボイラープレートコードを削ぎ落としてピュアなDecoratorを実現する鍵となります。また、具象デコレータが target への呼び出しを忘れると、それより内側の全ての装飾が「虚無に消え去る」という罠は、非常に恐ろしい呪いのようでした。
私の視界が涼やかな青いホログラムに照らされ、博士の隠したジャーキーを完璧に見つけ出したとき、このパターンの本当の「美しさ」を理解できた気がします。
