Da meno è esponenzialmente più
Se C ++ e Java riguardano le gerarchie di tipi e la tassonomia dei tipi, Go riguarda la composizione.
Da meno è esponenzialmente più
Se C ++ e Java riguardano le gerarchie di tipi e la tassonomia dei tipi, Go riguarda la composizione.
Risposte:
Vuol dire che dove useresti qualcosa nell'ordine di:
class A : public B {};
in qualcosa come Java o C ++, in Go useresti (qualcosa di equivalente a):
class A {
B b;
};
Sì, questo fornisce funzionalità simili all'ereditarietà. Espandiamo un po 'l'esempio sopra:
struct B {
int foo() {}
};
struct A {
B b;
};
A a;
a.foo(); // not allowed in C++ or Java, but allowed in Go.
Per fare ciò, tuttavia, si utilizza una sintassi non consentita in C ++ o Java: si lascia l'oggetto incorporato senza un nome proprio, quindi è più simile a:
struct A {
B;
};
Questa domanda / problema è simile a questa .
In Go, non hai davvero OOP.
Se vuoi "specializzare" un oggetto, lo fai incorporando, che è una composizione, ma con alcune chicche che lo rendono parzialmente simile all'eredità. Lo fai in questo modo:
type ConnexionMysql struct {
*sql.DB
}
In questo esempio, ConnexionMysql è un tipo di specializzazione di * sql.DB e puoi chiamare ConnexionMysql le funzioni definite su * 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)
Quindi a prima vista potresti pensare che questa composizione sia lo strumento per rendere la tua solita tassonomia.
Ma
se una funzione definita su * sql.DB chiama altre funzioni definite su * sql.DB, non chiamerà le funzioni ridefinite su ConnexionMysql anche se esistono.
Con l'eredità classica, fai spesso qualcosa del genere:
func (db *sql.DB) doComplexThing() {
db.doSimpleThing()
db.doAnotherSimpleThing()
}
func (db *sql.DB) doSimpleThing() {
// standard implementation, that we expect to override
}
Cioè, definisci doComplexThing
la super classe come un'organizzazione su chiamate delle specializzazioni.
Ma in Go, questo non chiamerebbe la funzione specializzata ma la funzione "superclasse".
Quindi, se vuoi avere un algoritmo che ha bisogno di chiamare alcune funzioni definite su * sql.DB ma ridefinite su ConnexionMySQL (o altre specializzazioni), non puoi definire questo algoritmo come una funzione di * sql.DB ma devi definirlo altrove e questa funzione comporrà solo le chiamate alla specializzazione fornita.
Puoi farlo in questo modo usando le interfacce:
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
}
Questo è abbastanza diverso dalla classica sostituzione delle gerarchie di classi.
In particolare, ovviamente non è possibile avere direttamente un terzo livello che eredita un'implementazione della funzione dal secondo.
In pratica, finirai per usare principalmente interfacce (ortogonali) e lasciare che la funzione componga le chiamate su un'implementazione fornita invece di avere la "superclasse" dell'implementazione che organizza tali chiamate.
Nella mia esperienza, ciò porta all'assenza pratica di gerarchie più profonde di un livello.
Troppo spesso, in altre lingue, hai il riflesso, quando vedi che il concetto A è una specializzazione del concetto B, per reimpostare questo fatto creando una classe B e una classe A come sottoclasse di B. Invece di creare il tuo programma attorno ai tuoi dati, passi il tempo a riprodurre la tassonomia degli oggetti nel tuo codice, sul principio che questa è realtà.
In Go non puoi definire un algoritmo generale e specializzarlo. È necessario definire un algoritmo generale e assicurarsi che sia generale e funzioni con le implementazioni dell'interfaccia fornite.
Essendo stato inorridito dalla crescente complessità di alcuni alberi della gerarchia su cui i programmatori stavano realizzando hack complessi per provare ad accogliere un algoritmo la cui logica implica infine tutti i livelli, direi che sono felice con la logica Go più semplice, anche se forza a pensare invece di semplicemente reificare i concetti del modello di applicazione.