Kiedy Rob Pike mówi „Go chodzi o kompozycję”, co dokładnie miał na myśli? [Zamknięte]

Odpowiedzi:

13

Ma na myśli to, że w przypadku użycia czegoś w kolejności:

class A : public B {};

w czymś takim jak Java lub C ++, w Go użyłbyś (coś równoważnego):

class A {
    B b;
};

Tak, zapewnia to funkcje podobne do dziedziczenia. Rozwińmy trochę powyższy przykład:

struct B {
    int foo() {}
};

struct A { 
    B b;
};

A a;

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

Aby to zrobić, używasz składni, która jest niedozwolona w C ++ lub Javie - pozostawiasz osadzony obiekt bez własnej nazwy, więc bardziej przypomina:

struct A {
   B;
};
Jerry Coffin
źródło
1
Jestem ciekawy, robię to w C ++ (preferuję kompozycję). Czy go udostępnia funkcje, które pomagają mi komponować w Java / C ++, gdybym musiał dziedziczyć?
Doug T.,
2
@DougT .: Tak, zredagowałem w przykładzie pokazującym ogólną ideę (części) tego, na co pozwala.
Jerry Coffin
2
Myślę, że nie ma to sensu: różnica nie jest tylko składniowa, co sugeruje, że używasz osadzania do budowania swojej taksonomii. Faktem jest, że brak zastąpienia metody OOP uniemożliwia zbudowanie klasycznej taksonomii i zamiast tego należy użyć kompozycji.
Denys Séguret,
1
@dystroy: W porównaniu z Javą prawdopodobnie masz rację. W porównaniu do C ++, nie tak bardzo - ponieważ (przynajmniej wśród tych, którzy mają wskazówkę) te gigantyczne taksonomie były ostatnio widziane blisko 20 lat temu.
Jerry Coffin
1
@dystroy: Nadal nie rozumiesz. Trzypoziomowa hierarchia we współczesnym języku C ++ jest niespotykana. W C ++ zobaczysz te w bibliotece iostreams i hierarchii wyjątków - ale blisko nigdzie indziej. Gdyby dzisiaj zaprojektowano bibliotekę iostreams, myślę, że można bezpiecznie powiedzieć, że tak też nie byłoby. Konkluzja: twoje argumenty pokazują mniej o C ++ niż o tym, jak jesteś z nim bez kontaktu. Biorąc pod uwagę, że nie używałeś go od dziesięcioleci, ma to sens. To, co nie ma sensu, to próba powiedzenia, jak używany jest C ++ w oparciu o to datowane doświadczenie.
Jerry Coffin
8

To pytanie / problem jest trochę podobne do tego .

W Go tak naprawdę nie masz OOP.

Jeśli chcesz „specjalizować” obiekt, robisz to przez osadzenie, które jest kompozycją, ale z pewnymi dodatkami, które częściowo przypominają dziedziczenie. Robisz to w ten sposób:

type ConnexionMysql struct {
    *sql.DB
}

W tym przykładzie ConnexionMysql jest rodzajem specjalizacji * sql.DB i można wywołać w ConnexionMysql funkcje zdefiniowane w * sql.DB:

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)

Na pierwszy rzut oka możesz więc pomyśleć, że ta kompozycja jest narzędziem do stworzenia zwykłej taksonomii.

Ale

jeśli funkcja zdefiniowana w * sql.DB wywołuje inne funkcje zdefiniowane w * sql.DB, nie wywoła funkcji przedefiniowanych w ConnexionMysql, nawet jeśli one istnieją.

W przypadku dziedziczenia klasycznego często robisz coś takiego:

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

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

Oznacza to, że definiujesz doComplexThingw superklasie jako organizację na wezwanie specjalizacji.

Ale w Go nie wywołałoby to funkcji specjalistycznej, ale funkcję „nadklasy”.

Jeśli więc chcesz mieć algorytm, który musi wywoływać niektóre funkcje zdefiniowane w * sql.DB, ale przedefiniowane w ConnexionMySQL (lub innych specjalizacjach), nie możesz zdefiniować tego algorytmu jako funkcji * sql.DB, ale musisz go zdefiniować gdzie indziej a ta funkcja będzie składać tylko połączenia z podaną specjalizacją.

Możesz to zrobić za pomocą interfejsów:

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
}

Jest to całkiem odmienne od klasycznego przesłonięcia hierarchii klas.

W szczególności, oczywiście nie możesz bezpośrednio mieć trzeciego poziomu dziedziczącego implementację funkcji od drugiego.

W praktyce przestaniesz używać głównie (ortogonalnych) interfejsów i pozwolisz, aby funkcja tworzyła wywołania na dostarczonej implementacji zamiast organizować te wywołania przez „nadklasę” implementacji.

Z mojego doświadczenia wynika, że ​​prowadzi to do praktycznego braku hierarchii głębszych niż jeden poziom.

Zbyt często w innych językach masz odruch, gdy widzisz, że pojęcie A jest specjalizacją pojęcia B, aby to potwierdzić, tworząc klasę B i klasę A jako podklasę B. Zamiast tworzyć programując wokół danych, spędzasz czas na odtwarzaniu taksonomii obiektów w kodzie, na zasadzie, że jest to rzeczywistość.

W Go nie można zdefiniować ogólnego algorytmu i go specjalizować. Musisz zdefiniować ogólny algorytm i upewnić się, że jest on ogólny i działa z dostarczonymi implementacjami interfejsu.

Będąc przerażonym rosnącą złożonością niektórych drzew hierarchicznych, na których koderzy robili skomplikowane włamania, aby dostosować się do algorytmu, którego logika w końcu implikuje wszystkie poziomy, powiedziałbym, że jestem zadowolony z prostszej logiki Go, nawet jeśli wymusza zamiast myśleć o koncepcjach modelu aplikacji, musisz pomyśleć.

Denys Séguret
źródło