Cuando Rob Pike dice "Go es sobre composición", ¿qué quiere decir exactamente? [cerrado]

12

De Menos es exponencialmente más

  

Si C ++ y Java tienen que ver con jerarquías de tipos y la taxonomía de tipos, Go tiene que ver con la composición.

    
pregunta CMinus 18.07.2012 - 16:34

2 respuestas

13

Quiere decir que donde usarías algo del orden de:

class A : public B {};

en algo como Java o C ++, en Go usarías (algo equivalente a):

class A {
    B b;
};

Sí, esto proporciona capacidades similares a la herencia. Expandamos un poco el ejemplo de arriba:

struct B {
    int foo() {}
};

struct A { 
    B b;
};

A a;

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

Para hacer esto, sin embargo, utiliza una sintaxis que no está permitida en C ++ o Java; deja el objeto incrustado sin un nombre propio, por lo que es más como:

struct A {
   B;
};
    
respondido por el Jerry Coffin 18.07.2012 - 17:05
8

Esta pregunta / problema es similar a éste .

En Go, realmente no tienes POO.

Si desea "especializar" un objeto, hágalo incrustando, que es una composición, pero con algunas ventajas, es parcialmente similar a la herencia. Lo haces así:

type ConnexionMysql struct {
    *sql.DB
}

En este ejemplo, ConnexionMysql es un tipo de especialización de * sql.DB, y puede llamar a ConnexionMysql las funciones definidas en * 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)

A primera vista, podría pensar que esta composición es la herramienta para hacer su taxonomía habitual.

Pero

si una función definida en * sql.DB llama a otras funciones definidas en * sql.DB, no llamará a las funciones redefinidas en ConnexionMysql incluso si existen.

Con la herencia clásica, a menudo haces algo como esto:

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

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

Es decir, usted define doComplexThing en la súper clase como una organización en las llamadas de las especializaciones.

Pero en Go, esto no llamaría la función especializada sino la función "superclase".

Entonces, si desea tener un algoritmo que necesite llamar a algunas funciones definidas en * sql.DB pero redefinido en ConnexionMySQL (u otras especializaciones), no puede definir este algoritmo como una función de * sql.DB pero debe defínalo en otro lugar y esta función solo compondrá las llamadas a la especialización proporcionada.

Puedes hacerlo así usando interfaces:

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
}

Esto es bastante diferente de la anulación clásica de las jerarquías de clase.

Especialmente, obviamente no puede tener directamente un tercer nivel heredando una implementación de la función del segundo.

En la práctica, terminará usando la mayoría de las interfaces (ortogonales) y dejará que la función componga las llamadas en una implementación proporcionada en lugar de tener la "superclase" de la implementación organizando esas llamadas.

En mi experiencia, esto conduce a la ausencia práctica de jerarquías más profundas que un nivel.

Muy a menudo, en otros idiomas, tiene el reflejo, cuando ve que el concepto A es una especialización del concepto B, para verificar este hecho mediante la creación de una clase B y una clase A como una subclase de B. Al crear su programa alrededor de sus datos, pasa tiempo reproduciendo la taxonomía de los objetos en su código, en el principio de que esto es realidad.

En Go no puedes definir un algoritmo general y especializarlo. Debe definir un algoritmo general y asegurarse de que sea general y que funcione con las implementaciones de interfaz proporcionadas.

Habiendo estado horrorizado por la creciente complejidad de algunos árboles jerárquicos en los que los programadores hacían hacks complejos para tratar de acomodar un algoritmo cuya lógica finalmente implica todos los niveles, diría que estoy feliz con la lógica Go más simple, incluso si te obliga a pensar, en lugar de simplemente reificar los conceptos de tu modelo de aplicación.

    
respondido por el Denys Séguret 18.07.2012 - 17:10

Lea otras preguntas en las etiquetas