プログラミングについて話すとき、私たちは通常、データを変更したり操作したりする一連の関数を書くことを意味します。オブジェクト指向プログラミング (OOP) は、データを含み、関連する機能がいくつか関連付けられている「オブジェクト」に焦点を当てたプログラミング モデルです。オブジェクト指向プログラミングには、継承、カプセル化、ポリモーフィズム、抽象化という 4 つの柱があります。このブログでは、Golang でそれぞれを実装する方法を例を挙げて見ていきます。 OOP に関するいくつかの基本的な考え方をお勧めしますが、そうでない場合は、4 つの柱すべてが何を意味するのかについて簡単に説明します。
オブジェクト指向プログラミングの核となる考え方は、次の箇条書きに要約できます。
これら 3 つの概念を理解するために、Golang のコードを見てみましょう:
package main import "fmt" type Batman struct { actor string year int } func (b Batman) SayImBatman() { fmt.Printf("I'm %s and I'm Batman from year %d\n", b.actor, b.year) } func main() { b1 := Batman{actor: "Michael Keaton", year: 1989} b2 := Batman{actor: "Christian Bale", year: 2005} b1.SayImBatman() b2.SayImBatman() }
Golang では、クラスは私たちが定義した型にすぎません。これらの型は必ずしも構造体である必要はありませんが、OOP では任意の型 (文字列、整数など) のデータのコレクションを扱うため、通常は構造体になります。
クラスはオブジェクトの設計図です。クラスをインスタンス化するたびに、オブジェクトが形成されます。この例では、b1 と b2 は Batman クラスのオブジェクトです。
SayImBatman 関数は、クラスの任意のオブジェクトに対して呼び出すことができます。これは Batman クラスに関連付けられているため、通常の関数と呼ぶのではなく、クラスのメソッドと呼ばれます。
これで OOP の基本が十分に理解できたので、次のセクションに進んで OOP の 4 つの柱について説明する必要があると思います。
継承では、OOP の 親 クラスと 子 クラスの概念が導入されます。子クラスは親クラスから派生したクラスであり、そのすべてのメソッドとプロパティ (データ) を継承します。これを理解するのに役立つコードをいくつか見てみましょう:
package main import "fmt" type Hero struct { team string } type Batman struct { Hero name string } type Ironman struct { Hero power int } func (h Hero) SayTeam() { fmt.Println("My Team is", h.team) } func (b Batman) SayImBatman() { fmt.Printf("I'm %s and I'm Batman\n", b.name) } func (i Ironman) SayPowerLevel() { fmt.Printf("I'm Ironman and my powerlevel is %d\n", i.power) } func main() { b1 := Batman{Hero{team: "Justice League"}, "Christian Bale"} i1 := Ironman{Hero{team: "Avengers"}, 23} b1.SayImBatman() b1.SayTeam() i1.SayPowerLevel() i1.SayTeam() }
この例では、バットマンとアイアンマンはヒーローの親クラスの子クラスです。これらは、親クラスのプロパティ (チーム) とそのメソッド (SayTeam) にアクセスできます。 b1 インスタンスと i1 インスタンスを宣言するときにわかるように、親クラスのプロパティと、それぞれのクラスの固有のプロパティを指定します。どちらも、親クラスで定義されている SayTeam メソッドを呼び出すことができます。ただし、それぞれに固有の個別のプロパティとメソッドもあります。
Golang は、合成を使用して (構造体内の構造体を使用して) 継承を実装します。 C や Java などの他の OOP 言語のような、クラスベースの継承が組み込まれていません。
カプセル化は、オブジェクトの内部プロパティを隠し、直接変更できないようにする原則です。代わりに、これらのプロパティを取得および更新するメソッドの提供に依存します。これをよりよく理解するために例を見てみましょう:
package main import "fmt" type Batman struct { actor string year int } func (b Batman) SayImBatman() { fmt.Printf("I'm %s and I'm Batman from year %d\n", b.actor, b.year) } func main() { b1 := Batman{actor: "Michael Keaton", year: 1989} b2 := Batman{actor: "Christian Bale", year: 2005} b1.SayImBatman() b2.SayImBatman() }
package main import "fmt" type Hero struct { team string } type Batman struct { Hero name string } type Ironman struct { Hero power int } func (h Hero) SayTeam() { fmt.Println("My Team is", h.team) } func (b Batman) SayImBatman() { fmt.Printf("I'm %s and I'm Batman\n", b.name) } func (i Ironman) SayPowerLevel() { fmt.Printf("I'm Ironman and my powerlevel is %d\n", i.power) } func main() { b1 := Batman{Hero{team: "Justice League"}, "Christian Bale"} i1 := Ironman{Hero{team: "Avengers"}, 23} b1.SayImBatman() b1.SayTeam() i1.SayPowerLevel() i1.SayTeam() }
Golang では、パッケージからエクスポートされるプロパティとメソッドは大文字で始まります。 utils パッケージでアクターと年を小文字で定義すると、それらを直接変更できないようになります。代わりに、main.go ファイルで見られるように、エクスポートされたメソッド (大文字で始まる) - GetActor、SetActor などを使用して、それらを取得して変更する必要があります。
これがカプセル化の意味です。データへの誤った変更を確実に防ぎ、その代わりにデータを安全に操作する方法を提供します。
異なる点の 1 つは、Batman クラスのすべてのメソッドで、前の例のように値レシーバー Batman の代わりにポインター レシーバー *Batman を使用していることです。これは、Set メソッドで元の構造体を変更できるようにするためです。また、Golang では、一部のメソッドでポインタ レシーバが必要な場合は、一貫性を保つためにすべてのメソッドでポインタ レシーバを使用することがベスト プラクティスです。そのため、Get メソッドは、元の構造体を変更していないにもかかわらず、ポインター レシーバーも使用しています。
また、もう 1 つ注意すべき点は、ポインター レシーバーを使用しているからといって、(&b1).GetActor を実行する必要はないということです。 Golang では、ポインター引数を持つ関数はポインターを受け取る必要がありますが、ポインター レシーバーを持つメソッドは値またはポインターをレシーバーとして受け取ることができます。
TL;DR: GetActor メソッドにはポインター レシーバーがあるため、Golang は自動的に b1.GetActor を (&b1).GetActor に変換しますが、GetActor が通常の関数であった場合には GetActor(b1) を GetActor(&b1) に変換しません。ポインタ引数。
OOP の次の 2 つの柱は、コード サンプルが非常に似ているため、クラブ化できます。ポリモーフィズムとは、2 つの異なるクラスの 2 つの異なるオブジェクトを同じ共通スーパークラスのオブジェクトとして扱うことができるプログラミング手法を指します。つまり、2 つの異なるオブジェクトに対して、それらが同じクラスのオブジェクトであるかのように、同じ関数を呼び出すことができます。これにより、インターフェイスが関与していることがわかり始めるはずです :)
これをよりよく理解するために、いくつかのコードを見てみましょう:
package main import "fmt" type Batman struct { actor string year int } func (b Batman) SayImBatman() { fmt.Printf("I'm %s and I'm Batman from year %d\n", b.actor, b.year) } func main() { b1 := Batman{actor: "Michael Keaton", year: 1989} b2 := Batman{actor: "Christian Bale", year: 2005} b1.SayImBatman() b2.SayImBatman() }
この例では、互いに関連性がないにもかかわらず、StartFight 関数に b1 オブジェクトと i1 オブジェクトの両方を渡すことができます。これが、子クラスが親クラスのメソッドにアクセスできる継承とどのように異なるのかを理解してください。この例では、子クラスと親クラスはありません (また、共有されるメソッドもありません)。代わりに、2 つの異なるオブジェクトが 関数 によって同じものとして扱われます。これはポリモーフィズムと呼ばれます。
さて、これも抽象化の一例として扱うことができます。名前が示すように、抽象化とは、実装の詳細を隠し、代わりに処理を行う関数だけを提供するプログラミング手法です。この例では、個々のヒーローのメソッドがどのように構成されているかを気にする必要はありません。ヒーローのいずれかのファイト機能を使用したい場合はいつでも、StartFight 機能を使用し続けることができます。このようにして、実装の詳細はユーザーから隠されたままとなり、重要な詳細のみが公開されます。
ここでポリモーフィズムに戻りますが、さらに 2 つの一般的な例があります。それらはメソッドのオーバーライドとオーバーロードです。
メソッドのオーバーライドとは、親クラスで定義されたメソッドの独自の実装を定義する子クラスを指します。この実装は、元の親クラスの実装の代わりに使用されるようになりました。先ほど継承に使用したコードを取り上げ、メソッドのオーバーライドでどのように見えるかを見てみましょう:
package main import "fmt" type Hero struct { team string } type Batman struct { Hero name string } type Ironman struct { Hero power int } func (h Hero) SayTeam() { fmt.Println("My Team is", h.team) } func (b Batman) SayImBatman() { fmt.Printf("I'm %s and I'm Batman\n", b.name) } func (i Ironman) SayPowerLevel() { fmt.Printf("I'm Ironman and my powerlevel is %d\n", i.power) } func main() { b1 := Batman{Hero{team: "Justice League"}, "Christian Bale"} i1 := Ironman{Hero{team: "Avengers"}, 23} b1.SayImBatman() b1.SayTeam() i1.SayPowerLevel() i1.SayTeam() }
このプログラムの出力は次のとおりです:
//oops-in-go/utils/utils.go package utils type Batman struct { actor string year int } func (b *Batman) GetActor() string { return b.actor } func (b *Batman) GetYear() int { return b.year } func (b *Batman) SetActor(actor string) { b.actor = actor } func (b *Batman) SetYear(year int) { b.year = year }
Batman クラスのオブジェクトは、親 Hero クラスのメソッドではなく、独自の SayTeam メソッドを使用するようになりました。 Ironman クラスには独自の SayTeam メソッドがないため、そのオブジェクトは依然として親クラスのメソッドを使用します。これがメソッドのオーバーライドを意味し、子クラスが親クラスで定義されたメソッドを「オーバーライド」します。
これは、同じ関数が複数の異なる引数を取ることができることを指します。これらの引数は、数や型が異なる場合があります。 Golang は、これを実現する 2 つの方法を提供します。1 つは可変引数関数を使用する方法、もう 1 つはインターフェイスを使用する方法です。
理解を深めるのに役立つ両方のコードを見てみましょう:
// oops-in-go/main.go package main import ( "fmt" "oops-in-go/utils" ) func main() { b1 := utils.Batman{} b1.SetActor("Michael Keaton") b1.SetYear(1989) fmt.Printf("I'm %s and I'm Batman from year %d\n", b1.GetActor(), b1.GetYear()) b1.SetActor("Christian Bale") b1.SetYear(2005) fmt.Printf("I'm %s and I'm Batman from year %d\n", b1.GetActor(), b1.GetYear()) }
ここで、任意の 数 の引数を使用して listMembers 関数を「オーバーロード」できます。
package main import "fmt" type Hero interface { Fight() } type Batman struct { weapon string } type Ironman struct { weapon string } func (b Batman) Fight() { fmt.Printf("Batman hits with a %s\n", b.weapon) } func (i Ironman) Fight() { fmt.Printf("Ironman hits with a %s\n", i.weapon) } func StartFight(h Hero) { fmt.Println("Fight has started.") h.Fight() } func main() { b1 := Batman{"Batarang"} i1 := Ironman{"Repulsor rays"} StartFight(b1) StartFight(i1) }
このプログラムの出力は次のとおりです:
package main import "fmt" type Hero struct { team string } type Batman struct { Hero name string } type Ironman struct { Hero power int } func (h Hero) SayTeam() { fmt.Println("My Team is", h.team) } func (b Batman) SayImBatman() { fmt.Printf("I'm %s and I'm Batman\n", b.name) } func (i Ironman) SayPowerLevel() { fmt.Printf("I'm Ironman and my powerlevel is %d\n", i.power) } func (b Batman) SayTeam() { fmt.Printf("I'm Batman and my team is %s\n", b.team) } func main() { b1 := Batman{Hero{team: "Justice League"}, "Christian Bale"} i1 := Ironman{Hero{team: "Avengers"}, 23} b1.SayImBatman() b1.SayTeam() i1.SayPowerLevel() i1.SayTeam() }
ここでは、saySomething メソッドを「オーバーロード」して、さまざまな型の引数を受け取ります。空のインターフェイスを引数として受け取ります。これは任意の型であり、switch case を使用してその型をチェックし、それに応じて出力を出力します。
これが長文だったことは承知しています。もし最後まで読んでいただけたなら、私が本当に幸せであることを知っていただきたいです :) オブジェクト指向プログラミングについて多くの新しいことを学んでいただければ幸いですそしてそれをGolangで実装する方法。私は自分のウェブサイトでさまざまな技術的概念に関するブログを書いています。新しいことを学ぶことに興味がある場合は、ニュースレターに登録することをお勧めします。
以上がGolang のオブジェクト指向プログラミング (OOP) の概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。