Go 継承

オブジェクト指向プログラミング(OOP)において、継承はあるクラス(サブクラス)が別のクラス(スーパークラス)から属性とメソッドを継承することを可能にするメカニズムです。継承により、サブクラスはスーパークラスのコードを再利用でき、スーパークラスを変更することなく、その振る舞いを拡張または変更できます。

Go 言語は伝統的なオブジェクト指向プログラミング言語ではなく、クラスと継承の概念がありません。

Go は構造体(struct)とインターフェース(interface)を使用して、同様の機能を実現します。


Go における「継承」

Go 言語には、伝統のなオブジェクト指向言語のようなクラス(class)と継承(inheritance)の概念はなく、代わりにコンポジション(composition)とインターフェース(interface)によって同様の機能を実現します。

1. コンポジション(Composition)

コンポジションは、Go でコードの再利用を実現する主要な方法です。ある構造体を別の構造体に埋め込むことで、子構造体は親構造体のフィールドとメソッドを「継承」できます。

例

package main

import "fmt"

// 親構造体
type Animal struct {
    Name string
}

// 親構造体のメソッド
func (a *Animal) Speak() {
    fmt.Println(a.Name, "says hello!")
}

// 子構造体
type Dog struct {
    Animal // Animal 構造体を埋め込む
    Breed  string
}

func main() {
    dog := Dog{
        Animal: Animal{Name: "Buddy"},
        Breed:  "Golden Retriever",
    }

    dog.Speak() // 親構造体のメソッドを呼び出す
    fmt.Println("Breed:", dog.Breed)
}

コードの説明

  • Animal親構造体であり、1つのフィールドとName1つのメソッドを含みます。Speak。
  • Dog子構造体であり、埋め込むことでAnimal構造体を継承し、Animalのフィールドとメソッドを継承します。
  • にmain関数内では、私たちはDogインスタンスを作成し、呼び出しましたSpeakメソッド。

2. インターフェース(Interface)

インターフェースは、Go でポリモーフィズムを実現する主要な方法です。インターフェースを定義することで、異なる構造体が同じメソッドを実装でき、継承に似たポリモーフィズムの動作を実現します。

サンプルコード

例

package main

import "fmt"

// インターフェースを定義
type Speaker interface {
    Speak()
}

// 親構造体
type Animal struct {
    Name string
}

// インターフェースメソッドを実装
func (a *Animal) Speak() {
    fmt.Println(a.Name, "says hello!")
}

// 子構造体
type Dog struct {
    Animal
    Breed string
}

func main() {
    var speaker Speaker

    dog := Dog{
        Animal: Animal{Name: "Buddy"},
        Breed:  "Golden Retriever",
    }

    speaker = &dog
    speaker.Speak() // インターフェース経由でメソッドを呼び出す
}

コードの説明

  • Speakerはインターフェースであり、1つのSpeakメソッドを定義しています。
  • Animal構造体はSpeakerインターフェースを実装しています。
  • Dog構造体は埋め込みによりAnimal構造体を間接的に実装して、Speakerインターフェースを実装しています。
  • にmain関数内では、DogインスタンスをSpeakerインターフェースに代入し、インターフェース経由でSpeakメソッドを呼び出しています。

Go と古典的な継承の違い

特性古典的な継承Go の方法
コードの再利用継承によるコンポジション(構造体の埋め込み)による
ポリモーフィズム継承とメソッドのオーバーライドによるインターフェースによる実装
関係「〜である」(is-a)関係「〜を持つ」(has-a)または「〜を実装する」関係
柔軟性継承関係は固定実行時にコンポジション可能

完全な継承のシミュレーション:

例

package main

import "fmt"

// 基底クラス
type Vehicle struct {
    Brand string
}

func (v *Vehicle) Start() {
    fmt.Println(v.Brand, "started")
}

// 派生クラス
type Car struct {
    Vehicle // Vehicle を埋め込む
    Model  string
}

// Start メソッドをオーバーライド
func (c *Car) Start() {
    fmt.Println(c.Brand, c.Model, "car started")
}

func main() {
    v := Vehicle{Brand: "Toyota"}
    c := Car{
        Vehicle: Vehicle{Brand: "Honda"},
        Model:  "Civic",
    }
   
    v.Start() // Toyota started
    c.Start() // Honda Civic car started
    c.Vehicle.Start() // Honda started
}

Go のこの設計は、脆い基底クラス問題など、伝統的な継承の多くの問題を回避し、同時に大きな柔軟性を提供します。

その他の拡張