「型とインターフェースの違いは何か」という問いは、実は比較の軸がずれています。インターフェースは型の対義語ではなく、型の一種だからです。整理すべきなのは、数ある型のなかでインターフェースだけが持つ性質は何か、という点です。
型は分類、インターフェースは契約
型とは、値についての制約と、その値に許される操作を決める規則です。i32 も String も User 構造体も、すべて型です。
そのうちインターフェースは、データの中身を一切決めず、満たすべき操作だけを列挙した型を指します。
// 具象型:データの形を決める
type User = { id: number; name: string };
// インターフェース:できることを決める
interface UserRepository {
findById(id: number): Promise<User | null>;
save(user: User): Promise<void>;
}
User は「そういう形のデータ」を表し、UserRepository は「そう振る舞える何か」を表します。前者からは値を作れますが、後者は単独では値を作れません。必ず実装が要ります。
具象型は「何であるか」を、インターフェースは「何ができるか」を表す。
状態を持たないから、いくつでも重ねられる
インターフェースが実装とフィールドを持たない——つまり状態を持たない——ことは、単なる制限ではなく設計上の効き目があります。
継承では親クラスの状態と実装を丸ごと引き継ぐため、多重継承を許すとどの実装を使うかが曖昧になります。インターフェースは持ち込む状態がないので、1 つの型がいくつ実装しても衝突しません。
#[derive(Debug, Clone, PartialEq)]
struct UserId(u64);
Debug・Clone・PartialEq という 3 つの契約を同時に満たしています。「Dog は Animal である」という 1 本の系統樹に押し込む代わりに、「表示できる」「複製できる」「比較できる」という能力を独立に積み上げる設計です。
ただし現代の言語はここを少し崩しています。Java の default メソッド、Rust の trait のデフォルト実装、C# の既定インターフェース実装は、インターフェース側に実装を書けます。それでも状態は持てないという一線は多くの言語で維持されています。
契約を満たす判定は言語で分かれる
ある型がインターフェースを満たしているかを、いつ・どう判定するかは言語設計の分岐点です。
| 方式 | 代表 | 満たし方 |
|---|---|---|
| 公称型 | Java、C#、Rust | implements や impl で明示的に宣言する |
| 構造的型付け | TypeScript、Go | 必要な操作を持っていれば自動的に満たす |
Go では、標準ライブラリの型が自作インターフェースを知らないうちに満たしていることがあります。宣言が要らないため、既存の型に後から契約を当てはめられるのが強みです。
一方の公称型は、形が同じでも意図が違えば別物として扱えます。Meter と Second がどちらも f64 を包んでいても混同されないのは、この方式の利点です。
インターフェースは実装側ではなく利用側のもの
実務でいちばん効くのはこの視点です。インターフェースは、それを実装するクラスの持ち物ではなく、それを呼び出す側の要求仕様です。
// 利用側(application 層)が「必要な操作」を定義する
trait UserRepository {
fn find_by_id(&self, id: u64) -> Option<User>;
}
// 実装側(infrastructure 層)がそれに合わせる
struct PostgresUserRepository { /* ... */ }
この向きにすると、依存の矢印が実装から利用側へ逆転します。UserService は PostgreSQL を知らないまま書けて、テストではメモリ実装に差し替えられます。
逆に「実装クラスの公開メソッドをそのまま写したインターフェース」は契約ではなく、ただの別名です。実装が 1 つしか存在せず今後も増える見込みがないなら、そのインターフェースは抽象化の役に立っていません。
補足:TypeScript の type と interface
同じ問いでも、TypeScript に限った話であれば別問題になります。どちらも型に名前を付ける構文で、オブジェクト型を書く分にはほぼ互換です。差は 3 点だけです。
interfaceは同名宣言がマージされる。外部ライブラリの型を後から拡張する用途に使うtypeはユニオン型・タプル・条件型を書ける。interfaceはオブジェクト型と関数型に限られるinterfaceのほうがエラーメッセージが読みやすい傾向がある
公開 API のオブジェクト形状は interface、ユニオン型を含むものは type、という使い分けで実用上は困りません。
まとめ
- インターフェースは型と並ぶ概念ではなく、操作だけを規定する型の一種
- 具象型は「何であるか」、インターフェースは「何ができるか」を表す
- 状態を持たないため、1 つの型が複数の契約を同時に満たせる
- 契約を満たす判定は、明示宣言が要る公称型と、形が合えば満たす構造的型付けに分かれる
- インターフェースは利用側の要求仕様として定義すると、依存の向きが逆転する
- 実装が 1 つしかないインターフェースは、抽象化ではなく別名にすぎない