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)
}
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() // インターフェース経由でメソッドを呼び出す
}
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
}
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 のこの設計は、脆い基底クラス問題など、伝統的な継承の多くの問題を回避し、同時に大きな柔軟性を提供します。
その他の拡張