Was genau meint Rob Pike mit „Go is about composition“? [geschlossen]

12

Aus weniger wird exponentiell mehr

Wenn es in C ++ und Java um Typhierarchien und die Taxonomie von Typen geht, geht es in Go um die Komposition.

CMinus
quelle

Antworten:

13

Er bedeutet, dass Sie etwas in der Reihenfolge verwenden würden:

class A : public B {};

In etwas wie Java oder C ++ würden Sie in Go Folgendes verwenden:

class A {
    B b;
};

Ja, dies bietet vererbungsähnliche Funktionen. Lassen Sie uns das obige Beispiel etwas erweitern:

struct B {
    int foo() {}
};

struct A { 
    B b;
};

A a;

a.foo();  // not allowed in C++ or Java, but allowed in Go.

Dazu verwenden Sie jedoch eine Syntax, die in C ++ oder Java nicht zulässig ist. Sie lassen das eingebettete Objekt ohne einen eigenen Namen, sodass es eher wie folgt aussieht:

struct A {
   B;
};
Jerry Sarg
quelle
1
Ich bin neugierig, ich mache das in C ++ (bevorzuge Komposition). Bietet go Funktionen, die mir beim Verfassen helfen, wenn ich in Java / C ++ erben müsste?
Doug T.
2
@DougT .: Ja, ich habe ein Beispiel bearbeitet, das die allgemeine Idee (eines Teils davon) zeigt, was es erlaubt.
Jerry Coffin
2
Ich denke, das geht am Rande vorbei: Der Unterschied ist nicht nur ein syntaktischer, der impliziert, dass Sie die Einbettung verwenden, um Ihre Taxonomie aufzubauen. Tatsache ist, dass das Fehlen einer außer Kraft gesetzten OOP-Methode Sie daran hindert, Ihre klassische Taxonomie aufzubauen, und Sie stattdessen Komposition verwenden müssen.
Denys Séguret
1
@dystroy: Im Vergleich zu Java haben Sie wahrscheinlich einen Punkt. Im Vergleich zu C ++ nicht so sehr, da diese riesigen Taxonomien (zumindest bei denen mit einer Ahnung) zuletzt vor fast 20 Jahren gesehen wurden.
Jerry Coffin
1
@dystroy: Du verstehst immer noch nicht. Eine dreistufige Hierarchie in modernem C ++ ist so gut wie unbekannt. In C ++ sehen Sie diese in der iostreams-Bibliothek und in der Ausnahmehierarchie - aber fast nirgendwo anders. Wenn die iostreams-Bibliothek heute entworfen worden wäre, kann man mit Sicherheit sagen, dass dies auch nicht so wäre. Fazit: Ihre Argumente zeigen weniger, was C ++ angeht, als was Sie betrifft. Angesichts der Tatsache, dass Sie es seit Jahrzehnten nicht mehr verwendet haben, ist dies sinnvoll. Was keinen Sinn ergibt, ist zu sagen, wie C ++ verwendet wird, basierend auf dieser veralteten Erfahrung.
Jerry Coffin
8

Diese Frage / Problem ist dieser ähnlich .

In Go hast du nicht wirklich OOP.

Wenn Sie ein Objekt "spezialisieren" möchten, müssen Sie es einbetten. Hierbei handelt es sich um eine Komposition, die jedoch teilweise der Vererbung ähnelt. Du machst es so:

type ConnexionMysql struct {
    *sql.DB
}

In diesem Beispiel ist ConnexionMysql eine Art Spezialisierung von * sql.DB, und Sie können auf ConnexionMysql die in * sql.DB definierten Funktionen aufrufen:

type BaseMysql struct {
    user     string
    password string
    database string
}

func (store *BaseMysql) DB() (ConnexionMysql, error) {
    db, err := sql.Open("mymysql", store.database+"/"+store.user+"/"+store.password)
    return ConnexionMysql{db}, err
}

func (con ConnexionMysql) EtatBraldun(idBraldun uint) (*EtatBraldun, error) {
    row := con.QueryRow("select pv, pvmax, pa, tour, dla, faim from compte where id=?", idBraldun)
    // stuff
    return nil, err
}

// somewhere else:
con, err := ms.bd.DB()
defer con.Close()
// ...
somethings, err = con.EtatBraldun(id)

Auf den ersten Blick könnte man denken, dass diese Komposition das Werkzeug ist, um Ihre übliche Taxonomie zu erstellen.

Aber

Wenn eine in * sql.DB definierte Funktion andere in * sql.DB definierte Funktionen aufruft, werden die in ConnexionMysql neu definierten Funktionen auch dann nicht aufgerufen, wenn sie vorhanden sind.

Bei der klassischen Vererbung tun Sie häufig Folgendes:

func (db *sql.DB) doComplexThing() {
   db.doSimpleThing()
   db.doAnotherSimpleThing()
}

func (db *sql.DB) doSimpleThing() {
   // standard implementation, that we expect to override
}

Das heißt, Sie definieren doComplexThingdie Superklasse als Organisation bei Aufrufen der Spezialisierungen.

In Go würde dies jedoch nicht die spezialisierte Funktion, sondern die "Superklasse" -Funktion aufrufen.

Wenn Sie also einen Algorithmus haben möchten, der einige in * sql.DB definierte, aber in ConnexionMySQL (oder anderen Spezialisierungen) neu definierte Funktionen aufrufen muss, können Sie diesen Algorithmus nicht als Funktion von * sql.DB definieren, sondern müssen ihn an anderer Stelle definieren und diese Funktion erstellt nur die Anrufe an die angegebene Spezialisierung.

Du könntest es so machen, indem du Interfaces benutzt:

type interface SimpleThingDoer {
   doSimpleThing()
   doAnotherSimpleThing()
}

func doComplexThing(db SimpleThingDoer) {
   db.doSimpleThing()
   db.doAnotherSimpleThing()
}

func (db *sql.DB) doSimpleThing() {
   // standard implementation, that we expect to override
}

func (db ConnexionMySQL) doSimpleThing() {
   // other implemenation
}

Dies unterscheidet sich erheblich von der klassischen Übersteuerung von Klassenhierarchien.

Insbesondere können Sie offensichtlich nicht direkt eine dritte Ebene haben, die eine Funktionsimplementierung von der zweiten erbt.

In der Praxis beenden Sie die Verwendung der meisten (orthogonalen) Schnittstellen und lassen die Aufrufe einer bereitgestellten Implementierung durch die Funktion komponieren, anstatt dass die "Superklasse" der Implementierung diese Aufrufe organisiert.

Nach meiner Erfahrung führt dies zu einem praktischen Fehlen von Hierarchien, die tiefer als eine Ebene liegen.

Zu oft, in anderen Sprachen, haben Sie den Reflex, wenn Sie sehen, dass das Konzept A eine Spezialisierung des Konzepts B ist, diese Tatsache zu wiederholen, indem Sie eine Klasse B und eine Klasse A als Unterklasse von B erstellen Um Ihre Daten zu verarbeiten, verbringen Sie Zeit damit, die Taxonomie von Objekten in Ihrem Code nach dem Prinzip zu reproduzieren, dass dies Realität ist.

In Go können Sie keinen allgemeinen Algorithmus definieren und ihn spezialisieren. Sie müssen einen allgemeinen Algorithmus definieren und sicherstellen, dass dieser allgemein ist und mit den bereitgestellten Schnittstellenimplementierungen funktioniert.

Ich war entsetzt über die zunehmende Komplexität einiger Hierarchiebäume, auf denen die Codierer komplexe Hacks durchgeführt haben, um einen Algorithmus zu implementieren, dessen Logik schließlich alle Ebenen impliziert Sie denken, anstatt nur die Konzepte Ihres Anwendungsmodells zu überarbeiten.

Denys Séguret
quelle