
【Go言語】implements 宣言は不要?「ダックタイピング」なインターフェースの衝撃
JavaやC#でインターフェースを使う時、まずは「契約(Contract)」としてインターフェースを定義し、それをクラスで「実装します!」と明示的に宣言(implements / :)しますよね。
しかし、Go言語には implements キーワードが存在しません。
では、どうやって「このクラスはこのインターフェースを満たしている」と判断するのでしょうか?
1. ダックタイピング:「アヒルみたいに歩くなら、それはアヒルだ」
Goのアプローチは 「構造的部分型(Structural Subtyping)」 、通称ダックタイピングと呼ばれるものです。
「もしそれがアヒルのように歩き、アヒルのように鳴くなら、それはアヒルである」
つまり、「あるインターフェースが要求するメソッドをすべて持っていれば、明示的な宣言がなくても、自動的にそのインターフェースを実装したとみなす」 というルールです。

2. コードで比較:Java/C# vs Go
「動物(Animal)」インターフェースと、それを実装する「犬(Dog)」クラスで比較してみましょう。
Java / C# の場合(明示的)
「私は Animal です!」と名乗る必要があります。
// C#
public interface IAnimal {
void Speak();
}
public class Dog : IAnimal // ← ここで明示的に宣言が必要!
{
public void Speak() {
Console.WriteLine("ワン!");
}
}
Go の場合(暗黙的)
Dog 構造体は Animal インターフェースのことなど知らなくても構いません。ただメソッドを持っていればいいのです。
// Go // 1. インターフェース定義 type Animal interface { Speak() } // 2. 構造体定義(Animalについては何も言及しない) type Dog struct {} // 3. メソッド実装(これで自動的に Animal を満たしたことになる) func (d Dog) Speak() { fmt.Println("ワン!") } // 使い方 func main() { var a Animal a = Dog{} // コンパイルOK! DogはSpeak()を持ってるから。 a.Speak() }
もし Dog から Speak() メソッドを削除したり、引数を変えたりすると、a = Dog{} の代入箇所でコンパイルエラーになります。「Dog は Animal インターフェースを満たしていません」と怒られます。
3. なぜこの仕様なのか?(Java/C#エンジニアへのメリット)
「明示しないと分かりにくくない?」と不安になるかもしれません。しかし、この設計には強力なメリットがあります。それは「利用する側(Consumer)がインターフェースを定義できる」という点です。
Java/C# の悩み
外部ライブラリのクラス SomeLibClass をテストでモックしたい時、そのクラスが ISomeLibClass を実装していなければ、モックを作るのは大変です(ラッパークラスを作ったりする必要があります)。
Go の解決策
外部ライブラリがインターフェースを提供していなくても構いません。あなたが自分のコードの中で、「このメソッドさえあればいい」というインターフェースを勝手に作ればいいのです。
// 外部ライブラリ(変更できない) type ExternalLogger struct { ... } func (l ExternalLogger) Log(msg string) { ... } // 自分のコード // ExternalLoggerに合わせて、勝手にインターフェースを作る type MyLogger interface { Log(msg string) } // この関数は、ExternalLoggerでも、自作のMockLoggerでも受け取れる! func DoSomething(logger MyLogger) { logger.Log("Hello") }
これにより、依存関係の結合度(Coupling) が劇的に下がります。
4. interface{} と any :究極の抽象型
Goには java.lang.Object や C#の object 型に相当する、「あらゆる型が入る型」があります。
それが interface{}(空のインターフェース) です。
メソッドの要件が何もない(空っぽ)ということは、「どんな型でもこの要件(虚無)を満たしている」 と言えるからです。
Go 1.18 からは、この interface{} のエイリアスとして any というキーワードが導入されました。Java/C#エンジニアにはこちらの方が馴染みやすいでしょう。
func PrintAnything(v any) { fmt.Println(v) } PrintAnything(100) // OK PrintAnything("hello") // OK PrintAnything(Dog{}) // OK
5. ダウンキャスト(型アサーション)
any 型に入れた値を、元の型に戻したい時はどうするでしょうか?
Javaの (String)obj や C#の obj as string に相当するのが 型アサーション です。
var i any = "hello" // Java: String s = (String) i; s := i.(string) // 成功すれば s に "hello" が入る // 安全なキャスト(C#の as に近い) // s, ok := i.(string) s, ok := i.(string) if ok { fmt.Println("文字列です:", s) } else { fmt.Println("文字列ではありません") }
ここでも、チャネルの時に出てきた「Comma-ok イディオム」が登場しますね!
まとめ
- 宣言不要: メソッドシグネチャが一致すれば、自動的に実装したとみなされる。
- 後出しジャンケンOK: クラス(構造体)を作った後に、それに合わせたインターフェースを定義しても機能する。
any(interface{}): JavaのObjectのように何でも入る型だが、使う時は型アサーションが必要。
JavaやC#では「設計段階でインターフェースをきっちり決める」ことが重要でしたが、Goでは「必要になったらインターフェースを切り出す」という、よりプラグマティック(実用的)なスタイルが好まれます。