web-dev-qa-db-ja.com

Goに継承の代わりに埋め込む

このデザイン決定についてどう思いますか?それにはどのような利点があり、どのような欠点がありますか?

リンク:

52
Casebash

コメントで、埋め込みのアイデアが「継承を完全に置き換える」のに十分かどうか疑問に思いました。その質問への答えは「はい」です。数年前、私はTcl OOシステムと呼ばれる Snit を使用して非常に簡単にプレイしました。これは、継承と除外の構成と委任を使用しました。SnitはまだGoのものとは大きく異なりますアプローチですが、その1つの点では、共通の哲学的根拠があります。これは、クラスの階層ではなく、機能と責任の断片を結合するためのメカニズムです。

他の人が述べたように、それは本当に言語デザイナーがサポートしたいプログラミングの実践の種類についてです。そのようなすべての選択には、独自の長所と短所があります。 「ベストプラクティス」は必ずしもここで当てはまる表現ではないと思います。最終的に誰かがGoの継承レイヤーを開発するのを見ることになるでしょう。

(Tclに精通している読者にとって、Snitは[incr Tcl]でした。 Tclは、少なくとも私の考え方では、委任に関するすべてです。)

29
Zac Thompson

Gang of 4 の重要な原則は、「継承よりも構成を優先する」です。 Gomakesフォローする;-).

36
Alex Martelli

継承の実際の用途は次のとおりです。

  • ポリモーフィズム

    • Goのインターフェースの「静的ダックタイピング」システムがこの問題を解決
  • 別のクラスから実装を借用する

    • これが埋め込みの目的です

Goのアプローチは厳密に1対1に対応していません。Javaの継承とポリモーフィズムのこの古典的な例を検討してください( これに基づいて ):

//roughly in Java (omitting lots of irrelevant details)
//WARNING: don't use at all, not even as a test

abstract class BankAccount
{
    int balance; //in cents
    void Deposit(int money)
    {
        balance += money;
    }

    void withdraw(int money)
    {
        if(money > maxAllowedWithdrawl())
            throw new NotEnoughMoneyException();
        balance -= money;
    }

    abstract int maxAllowedWithdrawl();
}

class Account extends BankAccount
{
    int maxAllowedWithdrawl()
    {
        return balance;
    }
}

class OverdraftAccount extends BankAccount
{
    int overdraft; //amount of negative money allowed

    int maxAllowedWithdrawl()
    {
        return balance + overdraft;
    }
}

ここでは、継承とポリモーフィズムが組み合わされており、基になる構造を変更せずにこれをGoに変換することはできません。

私はGoを深く掘り下げていませんが、次のようになると思います。

//roughly Go? .... no?
//for illustrative purposes only; not likely to compile
//
//WARNING: This is totally wrong; it's programming Java in Go

type Account interface {
    AddToBalance(int)
    MaxWithdraw() int
}

func Deposit(account Account, amount int) {
    account.AddToBalance(amount)
}

func Withdraw(account Account, amount int) error {
    if account.MaxWithdraw() < amount {
        return errors.New("Overdraft!")
    }
    account.AddToBalance(-amount)
    return nil
}

type BankAccount {
    balance int
}

func (account *BankAccount) AddToBalance(amount int) {
    account.balance += amount;
}

type RegularAccount {
    *BankAccount
}

func (account *RegularAccount) MaxWithdraw() int {
    return account.balance //assuming it's allowed
}

type OverdraftAccount {
    *BankAccount
    overdraft int
}

func (account *OverdraftAccount) MaxWithdraw() int {
    return account.balance + account.overdraft
}

注によると、GoでJavaを実行しているため、これは完全に間違ったコーディング方法です。 Goでそのようなことを書くとしたら、おそらくこれとはかなり異なる編成になるでしょう。

12
hasen

埋め込みは、自動委任を提供します。埋め込みではポリモーフィズムの形式が提供されないため、これだけでは継承を置き換えるのに十分ではありません。 Goインターフェースはポリモーフィズムを提供します。これは、慣れ親しんだインターフェースとは少し異なります(一部の人々は、それらをダックタイピングまたは構造タイピングに例えます)。

他の言語では、変更は広範囲に及ぶため変更が難しいため、継承階層は慎重に設計する必要があります。 Goは、強力な代替手段を提供しながら、これらの落とし穴を回避します。

OOP Goについてもう少し詳しく説明します: http://nathany.com/good

7
nathany

人々はGoへの埋め込みに関する情報へのリンクをリクエストしています。

埋め込みについて説明し、具体的な例を示した「Effective Go」ドキュメントを次に示します。

http://golang.org/doc/effective_go.html#embedding

この例は、Goのインターフェースと型をすでに十分に理解している場合により意味がありますが、インターフェースを一連のメソッドの名前と見なし、構造体をCの構造体と同様に考える場合は、偽造することができます。

構造体の詳細については、Go言語の仕様を参照してください。これは、構造体の名前のないメンバーを埋め込み型として明示的に言及しています。

http://golang.org/ref/spec#Struct_types

これまでは、フィールド名がソースコードに値を追加しない場合に、内部構造体にフィールド名を使用する必要なく、1つの構造体を別の構造体に配置する便利な方法としてのみ使用しました。以下のプログラミング演習では、提案と応答チャネルを持つタイプ内に提案タイプをバンドルしています。

https://github.com/ecashin/go-getting/blob/master/bpaxos.go#L

3
Ed Cashin

私はそれが好きです。

使用する言語は思考パターンに影響します。 (Cプログラマーに「ワードカウント」の実装を依頼するだけです。おそらくリンクリストを使用し、パフォーマンスのためにバイナリツリーに切り替えます。しかし、すべてのJava/Ruby/Pythonプログラマーはディクショナリー/ハッシュを使用します。言語は、他のデータ構造を使用することを考えることができないほど脳。)

継承では、構築する必要があります-抽象的なものから始めて、それを特定のものにサブクラス化します。実際の有用なコードは、クラスNレベルの深さに埋め込まれます。親クラスにドラッグしないとコードを再利用できないため、オブジェクトの「一部」を使用するのは難しくなります。

Goでは、この方法で(インターフェースを使用して)クラスを「モデル化」できます。ただし、この方法でコーディングすることはできません(できません)。

代わりに、埋め込みを使用できます。コードは、それぞれが独自のデータを持つ小さな孤立したモジュールに分割できます。これにより、再利用が簡単になります。このモジュール性は、「大きな」オブジェクトとはほとんど関係がありません。 (つまり、Goでは、Duckクラスについてさえ知らない "quack()"メソッドを書くことができます。しかし、典型的なOOP言語では、 "my Duck .quack()の実装は、Duckの他のメソッドに依存しません。」)

Goでは、これによりプログラマーは常にモジュール性について考える必要があります。これは、カップリングの低いプログラムにつながります。カップリングが低いため、メンテナンスがはるかに簡単になります。 (「あら、Duck.quack()は本当に長くて複雑ですが、少なくとも、それがDuckの他の部分に依存しないことはわかっています。」)

3

私はGoについて今学習していますが、あなたが意見を求めているので、私がこれまでに知っていることに基づいて意見を提供します。埋め込みはGoの他の多くの典型的なもののようです。これは、既存の言語で既に行われているベストプラクティスに対する明示的な言語サポートです。たとえば、Alex Martelliが指摘したように、4のギャングは「継承よりも構成を優先する」と述べています。 Goは継承を削除するだけでなく、C++/Java/C#よりも構成を簡単かつ強力にします。

「Goは、言語Xではまだできない新しいことは何も提供しない」、「なぜ別の言語が必要なのか」などのコメントに戸惑いました。ある意味では、Goはこれまではできなかった新しいことを提供していないように見えますが、別の意味では、Goは、すでに他の言語を使用しています。

3
Greg Graham