De Menos é Exponencialmente Mais
Se C ++ e Java são sobre hierarquias de tipos e a taxonomia de tipos, Go é sobre composição.
De Menos é Exponencialmente Mais
Se C ++ e Java são sobre hierarquias de tipos e a taxonomia de tipos, Go é sobre composição.
Respostas:
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;
};
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 doComplexThing
na 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.