De moins est exponentiellement plus
Si C ++ et Java concernent les hiérarchies de types et la taxonomie des types, Go concerne la composition.
De moins est exponentiellement plus
Si C ++ et Java concernent les hiérarchies de types et la taxonomie des types, Go concerne la composition.
Réponses:
Il signifie que lorsque vous utiliseriez quelque chose de l'ordre de:
class A : public B {};
dans quelque chose comme Java ou C ++, dans Go vous utiliseriez (quelque chose d'équivalent à):
class A {
B b;
};
Oui, cela offre des capacités de type héritage. Développons un peu l'exemple ci-dessus:
struct B {
int foo() {}
};
struct A {
B b;
};
A a;
a.foo(); // not allowed in C++ or Java, but allowed in Go.
Pour ce faire, cependant, vous utilisez une syntaxe non autorisée en C ++ ou Java - vous laissez l'objet incorporé sans son propre nom, c'est donc plus comme:
struct A {
B;
};
Cette question / problème est un peu similaire à celui-ci .
Dans Go, vous n'avez pas vraiment de POO.
Si vous voulez "spécialiser" un objet, vous le faites en incorporant, qui est une composition, mais avec quelques goodies qui le rendent partiellement similaire à l'héritage. Vous le faites comme ceci:
type ConnexionMysql struct {
*sql.DB
}
Dans cet exemple, ConnexionMysql est une sorte de spécialisation de * sql.DB, et vous pouvez appeler sur ConnexionMysql les fonctions définies sur * 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)
Donc, à première vue, vous pourriez penser que cette composition est l'outil pour faire votre taxonomie habituelle.
Mais
si une fonction définie sur * sql.DB appelle d'autres fonctions définies sur * sql.DB, elle n'appellera pas les fonctions redéfinies sur ConnexionMysql même si elles existent.
Avec l'héritage classique, vous faites souvent quelque chose comme ceci:
func (db *sql.DB) doComplexThing() {
db.doSimpleThing()
db.doAnotherSimpleThing()
}
func (db *sql.DB) doSimpleThing() {
// standard implementation, that we expect to override
}
Autrement dit, vous définissez doComplexThing
sur la super classe comme une organisation sur les appels des spécialisations.
Mais dans Go, cela n'appellerait pas la fonction spécialisée mais la fonction "superclasse".
Donc, si vous voulez avoir un algorithme nécessitant d'appeler certaines fonctions définies sur * sql.DB mais redéfinies sur ConnexionMySQL (ou d'autres spécialisations), vous ne pouvez pas définir cet algorithme en fonction de * sql.DB mais devez le définir ailleurs et cette fonction ne composera que les appels vers la spécialisation fournie.
Vous pouvez le faire comme ceci en utilisant des 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
}
Ceci est très différent de la substitution classique des hiérarchies de classes.
Surtout, vous ne pouvez évidemment pas avoir directement un troisième niveau héritant d'une implémentation de fonction du second.
En pratique, vous finirez par utiliser principalement des interfaces (orthogonales) et laisserez la fonction composer les appels sur une implémentation fournie au lieu d'avoir la "superclasse" de l'implémentation organisant ces appels.
D'après mon expérience, cela conduit à l'absence pratique de hiérarchies plus profondes qu'un niveau.
Trop souvent, dans d'autres langues, vous avez le réflexe, quand vous voyez que le concept A est une spécialisation du concept B, de réifier ce fait en créant une classe B et une classe A comme sous-classe de B. Au lieu de créer votre programme autour de vos données, vous passez du temps à reproduire la taxonomie des objets dans votre code, sur le principe que c'est la réalité.
Dans Go, vous ne pouvez pas définir un algorithme général et le spécialiser. Vous devez définir un algorithme général et vous assurer qu'il est général et qu'il fonctionne avec les implémentations d'interface fournies.
Ayant été horrifié par la complexité croissante de certains arbres de hiérarchie sur lesquels les codeurs faisaient des hacks complexes pour essayer d'accommoder un algorithme dont la logique implique finalement tous les niveaux, je dirais que je suis satisfait de la logique Go plus simple, même si elle oblige vous pensez au lieu de simplement réifier les concepts de votre modèle d'application.