場面
紀元前380年頃のアテネ。ソクラテスが死んで19年が経ち、プラトンはアカデメイアで弟子たちに問いかけていた。
「地下の洞窟を想像しなさい。生まれたときから鎖で縛られた囚人たちが、壁だけを見つめて生きている。彼らの背後には火が燃えており、その前を人々が像を持って通り過ぎていく。囚人たちが見るのは、壁に映る影だけだ。彼らにとって、その影こそが現実だ。」
プラトンはここで止まらなかった。一人の囚人が鎖を解かれて振り返る。眩しさに目をしかめながら、実物の像を初めて見る。そして洞窟の外に出て、太陽の光の下で本物の世界を見る。彼はいま気づく——洞窟の影は実在ではなかった。実在するものが壁に投影されたに過ぎなかった、と。
この寓話の背後にプラトンの哲学の核心がある。イデア論(Forms / Eidos)だ。
私たちが目にする個々の馬は、いずれも特定の馬だ。色があり、年齢があり、傷がある。いつか死ぬ。しかしプラトンに言わせれば、それらはすべて「馬のイデア(Form of Horse)」という永遠の雛形の不完全な模倣に過ぎない。「馬のイデア」は、実在する馬を観察して頭の中で作った抽象概念ではない。それは論理的に先行して実在し、個々の馬がそこから派生してくる。
これは直感に反する。私たちは経験から抽象を作ると思っている。たくさんの馬を見て「馬とはこういうものだ」と学ぶ、と。しかしプラトンは逆を主張した。イデアの方が先で、個物はその影だ。普遍が特殊に先行する。抽象が具体より根本的だ。
洞窟の比喩はこの関係を視覚化している。囚人(私たち)が見る影(個物・具体的なもの)は、実物の像(イデア)が火(存在の源)に照らされて投影されたものだ。影を研究しても本質には近づけない。本質は源にある。
答え
抽象は具体の後に来るのではなく、論理的に先行する。「何であるべきか」の定義(イデア・インターフェース)が先にあり、具体的な実装はその影として後から来る。設計において「何をすべきか」の仕様が「どうやるか」の実装より根本的だ。
CSへの翻訳
1994年。エーリヒ・ガンマ、リチャード・ヘルム、ラルフ・ジョンソン、ジョン・ブリシデス——通称「Gang of Four(GoF)」——が『Design Patterns』を出版した。その中核にある格言はプラトン的だ。「インターフェースに対してプログラムせよ、実装に対してではなく(Program to an interface, not an implementation)」。インターフェース(Form)に対してコードを書け。具体的なクラス(影)に依存するな。
2年後の1996年、ロバート・C・マーティン(通称 “Uncle Bob”)がSOLID原則の「D」を定式化した。依存性逆転原則(Dependency Inversion Principle, DIP)だ。「上位モジュールは下位モジュールに依存してはならない。両者とも抽象に依存すべきだ。」
これはプラトンの転倒そのものだ。
直感的な依存の方向:ビジネスロジック(上位)がデータベース実装(下位)に依存する。「ビジネスロジックはPostgreSQLを使う」。 DIPの転倒:ビジネスロジックがインターフェースを定義する。PostgreSQLの実装がそのインターフェースを実装する。ビジネスロジックは影(具体的DB)を見ず、イデア(インターフェース)だけを見る。
インターフェース分離原則(Interface Segregation Principle, ISP)もプラトン的な整合性を持つ。イデアはその利用者に必要な本質だけを含むべきだ。関係のない能力を詰め込んだ「太ったイデア」は、純粋なFormではなく、何かと何かが混じった混沌だ。
さらに2005年、アリスター・コバーンが「ヘキサゴナルアーキテクチャ(Ports & Adapters)」を発表した。アプリケーションのコアがポート(インターフェース)を定義する。外部システム(データベース、APIクライアント、UIフレームワーク)はアダプター(インターフェースの実装)を提供する。洞窟の比喩そのままだ。コア(囚人)はイデア(インターフェース)の世界に生き、外の世界との接続は影のシステム(アダプター)が担う。
設計への示唆
プラトン的な設計の実践で最も直接的なのはTDD(テスト駆動開発)だ。テストを先に書くとは、インターフェース(Form)を先に定義することだ。「このモジュールはこういう問いに、こういう答えを返すべきだ」——これがテストであり、イデアだ。その後に書く実装コードは、そのテスト(Form)を満たす影に過ぎない。
依存の方向を設計するとき、問いはこうなる。
❌ 影から始める(実装優先)
// まず SendGrid クライアントを使う実装クラスを書く
class SendGridNotificationService {
async notify(userId: string, message: string): Promise<void> {
await sendGridClient.sendEmail({ to: userId, body: message });
}
}
// ユースケースが具体的な実装に依存する
class WelcomeUserUseCase {
constructor(private notifier: SendGridNotificationService) {} // 影に依存
}
WelcomeUserUseCase は SendGrid を知っている。テストで実際のメールが飛ぶか、複雑なモックが必要になる。SendGrid を別のサービスに切り替えればユースケースも変わる。洞窟の囚人が影と鎖でつながっている状態だ。
✅ Formから始める(インターフェース優先)
// まずインターフェース(Form)を定義する
interface NotificationPort {
notify(userId: string, message: string): Promise<void>;
}
// ユースケースはFormだけを知る
class WelcomeUserUseCase {
constructor(private notifier: NotificationPort) {} // Formに依存
async execute(userId: string): Promise<void> {
const message = `ようこそ!アカウント登録が完了しました。`;
await this.notifier.notify(userId, message);
}
}
// テスト用スタブ:メールは飛ばない
class InMemoryNotification implements NotificationPort {
public sent: { userId: string; message: string }[] = [];
async notify(userId: string, message: string) {
this.sent.push({ userId, message });
}
}
// 本番用 SendGrid 版
class SendGridNotification implements NotificationPort { /* ... */ }
// Slack 通知版(新要件でも追加するだけ)
class SlackNotification implements NotificationPort { /* ... */ }
ユースケースは NotificationPort というFormを見ている。通知の実装が SendGrid であろうと Slack であろうと、ユースケースは一行も変わらない。テストには InMemoryNotification を使えるから、メール送信サーバーなしに高速に動かせる。
実用上の最大の利益は、ビジネスロジックのテストがインフラを必要としなくなることだ。洞窟の比喩の囚人がどの影を見ていようと、ビジネスの本質(ウェルカムメッセージを送るロジック)は変わらない。それが依存性逆転の実在感だ。
graph TB
subgraph "❌ 伝統的な依存方向(影が現実)"
UC1[WelcomeUserUseCase] -->|依存| SG[SendGridNotificationService<br/>具体的な実装]
SG -->|直結| API[SendGrid API]
note1[テストに本物のAPIが必要\n実装変更でユースケースが壊れる]
end
subgraph "✅ 依存性逆転(Formが現実)"
UC2[WelcomeUserUseCase] -->|依存| IF[NotificationPort<br/>interface]
SG2[SendGridNotification] -.->|implements| IF
MEM[InMemoryNotification] -.->|implements| IF
SL[SlackNotification] -.->|implements| IF
UC2 -.->|テスト時| MEM
UC2 -.->|本番時| SG2
note2[ユースケースはFormだけを知る\n実装は何でも差し替え可能]
end
style IF fill:#fffacd,stroke:#d4a017
style UC2 fill:#e8f5e9
style note2 fill:#e8f5e9
style note1 fill:#ffebee
プラトンが洞窟で語ったのは認識論の話だったが、GoFとマーティンとコバーンはそれをソフトウェア設計に変換した。インターフェースはイデアだ。具体的なクラスは影だ。囚人(ビジネスロジック)は太陽(ドメインの真実)を直接見る必要はない——正確な影(インターフェース)を通じて、ドメインの本質を操作できる。
プラトンにはもう一つ重要な概念がある。**想起(アナムネーシス)**だ。「学びとは新しい知識を獲得することではなく、魂が忘れていたイデアを思い出すことだ」とプラトンは『メノン』で語った。経験豊富な設計者がいい抽象化を「思いつく」とき、彼らはしばしば「これは前から存在していた気がする」と感じる。それは錯覚ではない——ドメインのイデアは、ソフトウェアを書く前から論理的に存在していた。設計者がやっているのは創造ではなく、発見だ。
このプラトン的「方向の転倒」(具体に先行する抽象、影が依存するイデア)は、第10話のカントによってさらに鋭く定式化される。カントは認識論で「対象が心に従う」というコペルニクス的転回を起こす。プラトンの「個物がイデアに従う」は、その先駆だ。第10話を読むと、ヘキサゴナルアーキテクチャや TDD が同じ「方向の転倒」の異なる表現であることが見えてくる。
設計するときの問いをひとつ変えるだけでいい。「このクラスを書こう、それからインターフェースを引き出せる」ではなく、「このユースケースが必要とするのは何か、それをインターフェースにしよう」。影から入るのをやめ、Formから入る。それだけで依存の矢印が逆転し、テストが軽くなり、将来の変更が局所化される。
問い:今あなたが設計しているクラスは、インターフェース(Form)から始まっているか、それとも実装(影)から始まっているか。後者なら、洞窟の中にいるということだ。