GoはOOP言語ですか?

オブジェクト指向設計は、コンピューティングのローマ数字です。
-Goの著者、Rob Pike。



画像



Steve Franciaによる「Goはオブジェクト指向言語ですか?」という記事の無料翻訳に注目してください著者はGoでOOPパラダイムを使用する機能について視覚的に説明しています。 私はすぐに、元の素材の特性のため、ほとんどのテキストを完全に再定式化する必要があり、どこかに独自のテキストを追加する必要があると警告しました。 翻訳フラグは削除しませんでした。



1.はじめに



「オブジェクト指向」とはどういう意味ですか? OOPの概念の歴史を見て、それを理解してみましょう。

最初のオブジェクト指向言語Simulaは、60年代に登場しました。 彼は、オブジェクト、クラス、継承および子孫クラスの概念、仮想メソッド、 コルーチンなどの概念を紹介しました。 キャンペーンにとって、データ抽象化のパラダイムは最も価値のある貢献でした。



Simulaに精通していないかもしれませんが、間違いなく、それがインスピレーションとなった言語のいくつかを間違いなく知っています-Java、C ++、C#、Smalltalk、そしてその後、Objective-C、Python 、Ruby、Javascript、Scala、PHP、Perl ...完全なリストには、ほぼすべての一般的な現代言語が含まれています。 このパラダイムは私たちの生活にしっかりと定着しているため、現代のプログラマーのほとんどはそう思いませんでした。



OOPの一般に受け入れられている定義はないため、議論を続けるために独自の定式化を行います。



プログラムテキスト内のコードとデータを明示的に分離する代わりに、オブジェクト指向システムは「オブジェクト」の概念を使用してそれらを結合します。 オブジェクトは、状態(データ)と動作(コード)を含む抽象データ型です。


元の実装には、ほとんどすべての派生言語で採用された継承およびポリモーフィズムのメカニズムがあったため、通常、オブジェクト指向プログラミングの定義にはそれらが必要条件として含まれています。



次に、Goがオブジェクト、ポリモーフィズム、継承をどのように使用するかを見ていきます。その後、質問への回答が簡単になります。



2.オブジェクト?



object



にはGoという名前はありませんが、これは重要ではありません。 型object



は検出されませんが、オブジェクト指向アプローチの定義に該当する型があります。状態と動作の両方を含むデータ構造であり、それらはstruct



として示されます。 structは、名前付きフィールドとメソッドを含む型です。



明確にするために、例を挙げます。

 type rect struct { width int height int } func (r *rect) area() int { return r.width * r.height } func main() { r := rect{width: 10, height: 5} fmt.Println("area: ", r.area()) }
      
      





最初のブロックは、2つの整数フィールドを含む構造型の新しいrect



型を定義します。 次のブロックは、 area



関数を定義し、 rect



型にアタッチすることにより、この構造のメソッドを定義します。 より正確には、実際には、関数はrect



ポインター型に付加されます。

最後のブロックはプログラムのエントリポイントであり、これがメイン関数です。 最初の行は、 rect



新しいインスタンスを作成します(Goでは、選択したインスタンス作成方法(複合リテラルを使用)が最も便利です)。 2行目は、 r



の値で関数area



を呼び出した結果を出力します。



個人的に、それはすべて、オブジェクトを扱う多くのことを思い出しました。 構造化されたデータのタイプを作成し、それらのいくつかを操作するためのメソッドを定義できます。

他に不足しているものはありますか? はい、ほとんどのオブジェクト指向言語では、継承をサポートするクラスを使用してオブジェクトを記述します。これらのクラスのインターフェイスを定義して、クラス階層のツリーを定義することをお勧めします(単純な継承の場合)。



2.継承とポリモーフィズム



オブジェクト間の関係を定義するには、いくつかの異なるアプローチがあります。 いくつかの違いがありますが、それらはすべてコードの再利用のためにほぼ同じ目的で使用されます。



2.1。 単純な多重継承

継承は、既存の(ベース)クラスに基づいて新しいクラスを記述することができる言語メカニズムです。 基本クラスの数に基づいて、2種類の継承があります。 基本的な違いは、多重継承を使用した結果を評価することによってのみ感じることができます。単一継承階層はツリーであり、多重継承はラティスを生成します。 非常にシンプルをサポートする言語にはPHP、C#、Java、Rubyが含まれ、多重継承をサポートする言語にはPerl、Python、C ++が含まれます。



2.2。 サブタイプ多型

一部の言語では、サブタイプと継承の概念が密接に絡み合っているため、サブタイプと継承の違いはほとんど目立ちません。 実際、サブタイプは2つ以上のオブジェクト間の意味的関係を定義し、それによってis-a関係を形成します。 つまり、タイプAは、仕様Aが仕様Bに続き、仕様Aを満たすオブジェクト(またはクラス)も仕様Bを満たす場合、 Bのサブタイプです 単純な継承は実装を再利用するだけであるため、構文糖を提供しますが、それ以上は提供しません。



実装による継承と、サブタイプの多型による「継承」を明確に区別する必要があります。これは、以下のテキストから明らかです。



2.3。 構成

構成では、1つのオブジェクトは、その中に他のオブジェクトを含めることによって決定されます。つまり、継承するのではなく、単にそれらを含むだけです。 このタイプの関係はhas-aと呼ばれ、含まれるオブジェクトはメンバーシップルールに従います。



3. Goには継承がありますか?



Goは、通常の意味での継承なしに設計されました。 オブジェクト(構造)に関係がないことを意味するのではなく、言語の作成者がそのような指定に異なるメカニズムを選択しただけです。 多くの初心者がGoで書くと、このソリューションは深刻な欠陥のように思えるかもしれませんが、実際には言語の最も快適な機能の1つであり、多くの問題を解決すると同時に、数十年前の継承に関する論争を完全に解決します。



4.「単純な継承を破棄する方が良い」



次に、JavaWorldの記事フラグメントが悪である理由」の一部を紹介します。

設計パターンに関する4つのギャングの本では、実装による継承(拡張)からインターフェイス(実装)による継承への置き換えについて詳しく説明しています。



私はかつて、Javaグループユーザーの集まりを訪れました。そこでは、ジェームズゴスリング(Javaの作成者)が講演をするよう招待されました。 思い出に残るQ&Aセッション中に、誰かが彼に尋ねました:「もしJavaを新しくできたら、何を変えますか?」 「クラスを投げます」と彼は答えた。 聴衆の笑い声が落ちた後、彼は本当の問題は本質的にクラスではなく実装による継承にあると説明した(関係を拡張する)。 インターフェイス(実装関係)を介した継承が推奨されますが、実装を介した継承は可能な限り避けてください。


5. Goのオブジェクトの関係



5.1。 タイプ構成

通常の継承の代わりに、Go は継承の代わり構成の原則を厳密に使用し、構造とインターフェイスの関係はis-aとhas-aの原則に基づいて構築されます。 ここで使用されるオブジェクト構成メカニズムはインラインタイプのように見えるため、Goでは、has-a関係を作成しながら、構造に構造を埋め込むことができます。 良い例は、以下のコードのPerson型とAddress型の関係です。

 type Person struct { Name string Address Address } type Address struct { Number string Street string City string State string Zip string } func (p *Person) Talk() { fmt.Println("Hi, my name is", p.Name) } func (p *Person) Location() { fmt.Println("Im at", p.Address.Number, p.Address.Street, p.Address.City, p.Address.State, p.Address.Zip) } func main() { p := Person{Name: "Steve"} p.Address = Address{ Number: "13", Street: "Main" } p.Address.City = "Gotham" p.Address.State = "NY" p.Address.Zip = "01313" p.Talk() p.Location() }
      
      





結果:

 > Hi, my name is Steve > Im at 13 Main Gotham NY 01313
      
      





play.golang.org/p/5TVBDR7AYo



この例では、AddressがPerson



内にある間は別個のエンティティのままであることが重要です。 main



関数は、アドレスオブジェクトをp.Address



に割り当て、フィールドをドットで初期化して初期化する方法を示します。



5.2。 サブタイプ多型の模倣



著者による注意。 この記事の最初のバージョンでは、匿名構造フィールドを介したis-a関係の実装について誤って説明していました。 実際には、包含構造に存在するかのように、含まれるメソッドとプロパティが外部から見えるようになるため、これはis-a関係にのみ似ています。 以下に説明する理由により、このアプローチはis-aではありませんが、Goはis-aをサポートしており、 interfacesを介して実現されます 。 記事の現在のバージョンでは、匿名フィールドをis-aの模倣と呼んでいます。これは、多態性のメカニズムに似ていますが、それ以上ではありません。 //



is-a関係のシミュレーションも同じように機能します。 人( Person



)に話す方法を教えてください。 Citizen



は人( Person



)なので、話す方法( Talk



)も知っています。 これを念頭に置いて、前の例を拡張します。

 type Citizen struct { Country string Person //     } func (c *Citizen) Nationality() { fmt.Println(c.Name, "is a citizen of", c.Country) } func main() { c := Citizen{} c.Name = "Steve" c.Country = "America" c.Talk() c.Nationality() }
      
      





結果:

 > Hi, my name is Steve > Steve is a citizen of America
      
      





play.golang.org/p/eCEpLkQPR3



匿名構造フィールド、この場合はCitizen



Person



フィールド(タイプのみが示されている)を使用して、is-a関係の模倣を設定します。 Citizen



タイプは、 Person



タイプのすべてのプロパティとメソッドを取得しており、これらのプロパティとメソッドの一部を独自のもので補足または重複させることができます。 たとえば、都市の住民の反応を少し変えてみましょう。

 func (c *Citizen) Talk() { fmt.Println("Hello, my name is", c.Name, "and Im from", c.Country) }
      
      





結果:

 > Hello, my name is Steve and Im from America > Steve is a citizen of America
      
      





play.golang.org/p/jafbVPv5H9



現在、 main



メソッドは*Citizen.Talk()



代わりに*Person.Talk()



呼び出しています。



6.匿名フィールドがポリモーフィズムを与えない理由



2つの理由があります。



6.1。 各組み込み型の個々のフィールドへのアクセスを維持

実際、これはそれほど悪いことではありません。複数の「継承」を使用すると、親クラスのどのメソッドが呼び出されるかが明確にならないからです。 匿名フィールドを使用する場合、Goは型名を複製する補助フィールドを作成するため、すべての匿名フィールドの個々のメソッド、つまり継承メカニズムのシミュレーションの基本クラスを常に参照できます。 例:

 func main() { c := Citizen{} c.Name = "Steve" c.Country = "America" c.Talk() // <-   c.Person.Talk() // <-   c.Nationality() }
      
      





結果:

 > Hello, my name is Steve and Im from America > Hi, my name is Steve > Steve is a citizen of America
      
      







6.2。 子孫型は祖先型になりません

ポリモーフィズムが実在する場合、匿名フィールドによって包含型が包含型になりますが、Goでは、包含型と包含型の両方が完全に分離されたままになります。 一度見た方が良い場合、例を挙げてください:

 type A struct { } type B struct { A // B is-a A } func save(A) { // xxx } func main() { b := &B{} save(b) // OOOPS! b IS NOT A }
      
      





結果:

 > prog.go:17: cannot use b (type *B) as type A in function argument > [process exited with non-zero status]
      
      





play.golang.org/p/EmodogIiQU



この例は、Hacker Newsのこの解説で提案されています。 ありがとう、Optymizer。



7.実際のサブタイプ多型



Goのインターフェイスは本質的に非常にユニークです。 このセクションでは、ポリモーフィズムを実装するためのインターフェイスの使用に焦点を当てますが、これは彼らの主な関心事ではありません。



先ほど書いたように、ポリモーフィズムはis-aの関係です。 Goでは、各タイプは独立しており、そのような別のタイプに偽装することはできませんが、両方のタイプが同じインターフェースに適合することができます。 インターフェイスは、パラメーターとして関数とメソッドに渡すことができ、これにより、タイプ間の真のis-a関係を確立できます。



タイプがインターフェースに対応しているという事実は、インターフェースが必要とするメソッドのセットと、チェックされているタイプで利用可能なメソッドのセットを比較することにより、自動的にチェックされます。 言い換えれば、そのような態度は「何かがそれを行うことができれば、それをここで使用することができる」、つまり、 アヒルのタイピングの原則があると説明できます



前の例に戻って、新しいSpeakTo



関数を追加し、 main



Citizen



およびPerson



インスタンスに順番に適用しようとします。

 func SpeakTo(p *Person) { p.Talk() } func main() { p := &Person{Name: "Dave"} c := &Citizen{Person: Person{Name: "Steve"}, Country: "America"} SpeakTo(p) SpeakTo(c) }
      
      





結果:

 > Running it will result in > prog.go:48: cannot use c (type *Citizen) as type *Person in function argument > [process exited with non-zero status]
      
      





play.golang.org/p/fkKz0FkaEk



予想どおり、設計は単純に間違っています。 この場合、 Citizen



共通のプロパティを持っているにもかかわらず、 Person



Citizen



ません。 ただし、 Human



インターフェースを追加し、それをSpeakTo



関数の受け入れられるタイプにし、すべてが計画どおりに進んだことを確認します。

 type Human interface { Talk() } func SpeakTo(h Human) { h.Talk() } func main() { p := &Person{Name: "Dave"} c := &Citizen{Person: Person{Name: "Steve"}, Country: "America"} SpeakTo(p) SpeakTo(c) }
      
      





結果:

 > Hi, my name is Dave > Hi, my name is Steve
      
      





play.golang.org/p/vs92w57c5-



要約すると、Goのポリモーフィズムについて2つの重要なポイントを作成できます。





8.まとめ



上記の例でわかるように、オブジェクト指向アプローチの基本原則は関連しており、Goでうまく適用されています。 他の古典的なOOP言語とは少し異なるメカニズムが使用されているため、用語に違いがあります。 そのため、1つのエンティティで状態と動作を組み合わせるには、指定された(アタッチされた)メソッドを持つ構造が使用されます。 型間の関係を示すために、合成が使用されます。これにより、コード内の繰り返しの数が最小限に抑えられ、従来の継承の混乱から解放されます。 Goで型間のis-a関係を確立するために、不必要な言葉や逆効果的な説明なしでインターフェースが使用されます。



オブジェクトなしのオブジェクト指向プログラミングの新しいモデルに会いましょう!



〜Xlabによる翻訳と適応。

参照: 「Goでのコードの再利用と例」

ところで、念のため、この引用をここに引用します。

オブジェクト指向という用語を発明しましたが、C ++を念頭に置いていなかったことがわかります。
-アラン・ケイ



All Articles