Quando Rob Pike diz "Go é sobre composição", o que exatamente ele quer dizer? [fechadas]


Respostas:


13

Ele quer dizer que onde você usaria algo da ordem de:

class A : public B {};

em algo como Java ou C ++, no Go você usaria (algo equivalente a):

class A {
    B b;
};

Sim, isso fornece recursos semelhantes a herança. Vamos expandir um pouco o exemplo acima:

struct B {
    int foo() {}
};

struct A { 
    B b;
};

A a;

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

Para fazer isso, no entanto, você usa uma sintaxe que não é permitida em C ++ ou Java - você deixa o objeto incorporado sem um nome próprio, por isso é mais como:

struct A {
   B;
};

1
Estou curioso, faço isso em C ++ (prefiro composição). Fornece recursos que me ajudam a compor quando em Java / C ++ eu precisaria herdar?
Doug T.

2
@DougT .: Sim, editei em um exemplo que mostra a ideia geral de (parte) do que ela permite.
Jerry Coffin

2
Acho que isso erra o ponto: a diferença não é apenas sintática, o que implica que você use a incorporação para construir sua taxonomia. O fato é que a falta de substituição do método OOP impede que você construa sua taxonomia clássica e você deve usar a composição.
Denys Séguret

1
@dystroy: Comparado ao Java, você provavelmente tem razão. Comparado ao C ++, nem tanto - porque (pelo menos entre aqueles com uma pista) essas taxonomias gigantes foram vistas pela última vez há 20 anos.
Jerry Coffin

1
@dystroy: Você ainda não está entendendo. Uma hierarquia de três níveis no C ++ moderno é quase inédita. No C ++, você verá aqueles na biblioteca iostreams e na hierarquia de exceções - mas perto de nenhum outro lugar. Se a biblioteca iostreams estava sendo projetada hoje, acho seguro dizer que também não seria assim. Conclusão: seus argumentos mostram menos sobre C ++ do que sobre como você está desconectado. Dado que você não o usa há décadas, isso faz sentido. O que não faz sentido é tentar dizer como o C ++ é usado com base nessa experiência datada.
Jerry Coffin

8

Esta pergunta / problema é semelhante a esta .

No Go, você realmente não tem OOP.

Se você deseja "especializar" um objeto, faça-o incorporando, que é uma composição, mas com algumas guloseimas tornando-o parcialmente semelhante à herança. Você faz assim:

type ConnexionMysql struct {
    *sql.DB
}

Neste exemplo, ConnexionMysql é um tipo de especialização de * sql.DB e você pode chamar no ConnexionMysql as funções definidas em * 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)

Portanto, à primeira vista, você pode pensar que essa composição é a ferramenta para criar sua taxonomia usual.

Mas

se uma função definida no * sql.DB chamar outras funções definidas no * sql.DB, ela não chamará as funções redefinidas no ConnexionMysql, mesmo que elas existam.

Com a herança clássica, você costuma fazer algo assim:

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

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

Ou seja, você define doComplexThingna superclasse como uma organização nas chamadas das especializações.

Mas no Go, isso não chamaria a função especializada, mas a função "superclasse".

Portanto, se você deseja ter um algoritmo que precisa chamar algumas funções definidas no * sql.DB, mas redefinidas no ConnexionMySQL (ou outras especializações), não é possível definir esse algoritmo como uma função do * sql.DB, mas deve defini-lo em outro local e essa função apenas comporá as chamadas para a especialização fornecida.

Você poderia fazer assim 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
}

Isso é bem diferente da substituição clássica das hierarquias de classes.

Especialmente, você obviamente não pode ter diretamente um terceiro nível herdando uma implementação de função do segundo.

Na prática, você terminará usando principalmente interfaces (ortogonais) e permitirá que a função componha as chamadas em uma implementação fornecida, em vez de ter a "superclasse" da implementação organizando essas chamadas.

Na minha experiência, isso leva à ausência prática de hierarquias mais profundas que um nível.

Muitas vezes, em outros idiomas, você tem o reflexo, quando vê que o conceito A é uma especialização do conceito B, para reificar esse fato criando uma classe B e uma classe A como uma subclasse de B. Em vez de criar seu programa em torno de seus dados, você gasta tempo reproduzindo a taxonomia de objetos em seu código, com o princípio de que isso é realidade.

No Go, você não pode definir um algoritmo geral e especializá-lo. Você deve definir um algoritmo geral e garantir que ele seja geral e funcione com as implementações de interface fornecidas.

Tendo ficado horrorizado com a crescente complexidade de algumas árvores hierárquicas nas quais os codificadores estavam fazendo hacks complexos para tentar acomodar um algoritmo cuja lógica finalmente implica todos os níveis, eu diria que estou feliz com a lógica Go mais simples, mesmo que isso force você pensa em vez de apenas reificar os conceitos do seu modelo de aplicativo.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.