web-dev-qa-db-fra.com

Incorporation au lieu de l'héritage dans Go

Quelle est votre opinion sur cette décision de conception? Quels sont ses avantages et quels inconvénients?

Liens:

52
Casebash

Dans un commentaire, vous vous êtes demandé si l'idée d'intégration était suffisante pour "remplacer complètement l'héritage". Je dirais que la réponse à cette question est "oui". Il y a quelques années, j'ai joué très brièvement avec un système Tcl OO appelé Snit , qui utilisait la composition et la délégation à l'exclusion de l'héritage. Snit est toujours très différent de Go. mais, à cet égard, ils ont une base philosophique commune. C'est un mécanisme pour réunir des éléments de fonctionnalité et de responsabilité, pas une hiérarchie pour les classes.

Comme d'autres l'ont dit, il s'agit vraiment du type de pratiques de programmation que les concepteurs de langage souhaitent prendre en charge. Tous ces choix ont leurs avantages et leurs inconvénients; Je ne pense pas que les "meilleures pratiques" soient une expression qui s'applique nécessairement ici. Nous verrons probablement quelqu'un développer éventuellement une couche d'héritage pour Go.

(Pour tous les lecteurs familiers avec Tcl, je pensais que Snit correspondait un peu plus à la "sensation" de la langue que [incr Tcl] était. Tcl est tout au sujet de la délégation, au moins à ma façon de penser.)

29
Zac Thompson

Le principe crucial de Gang of 4 est "préférer la composition à l'héritage"; Go fait vous le suivez ;-).

36
Alex Martelli

Les seules utilisations réelles de l'héritage sont:

  • Polymorphisme

    • Le système de "typage statique du canard" de l'interface de Go résout ce problème
  • Emprunter l'implémentation d'une autre classe

    • C'est à cela que sert l'intégration

L'approche de Go ne correspond pas exactement à 1 à 1, considérez cet exemple classique d'héritage et de polymorphisme dans Java ( basé sur cela ):

//roughly in Java (omitting lots of irrelevant details)
//WARNING: don't use at all, not even as a test

abstract class BankAccount
{
    int balance; //in cents
    void Deposit(int money)
    {
        balance += money;
    }

    void withdraw(int money)
    {
        if(money > maxAllowedWithdrawl())
            throw new NotEnoughMoneyException();
        balance -= money;
    }

    abstract int maxAllowedWithdrawl();
}

class Account extends BankAccount
{
    int maxAllowedWithdrawl()
    {
        return balance;
    }
}

class OverdraftAccount extends BankAccount
{
    int overdraft; //amount of negative money allowed

    int maxAllowedWithdrawl()
    {
        return balance + overdraft;
    }
}

Ici, l'héritage et le polymorphisme sont combinés, et vous ne pouvez pas traduire cela en Go sans changer la structure sous-jacente.

Je ne me suis pas plongé profondément dans Go, mais je suppose que cela ressemblerait à quelque chose comme ceci:

//roughly Go? .... no?
//for illustrative purposes only; not likely to compile
//
//WARNING: This is totally wrong; it's programming Java in Go

type Account interface {
    AddToBalance(int)
    MaxWithdraw() int
}

func Deposit(account Account, amount int) {
    account.AddToBalance(amount)
}

func Withdraw(account Account, amount int) error {
    if account.MaxWithdraw() < amount {
        return errors.New("Overdraft!")
    }
    account.AddToBalance(-amount)
    return nil
}

type BankAccount {
    balance int
}

func (account *BankAccount) AddToBalance(amount int) {
    account.balance += amount;
}

type RegularAccount {
    *BankAccount
}

func (account *RegularAccount) MaxWithdraw() int {
    return account.balance //assuming it's allowed
}

type OverdraftAccount {
    *BankAccount
    overdraft int
}

func (account *OverdraftAccount) MaxWithdraw() int {
    return account.balance + account.overdraft
}

Selon la note, c'est totalement une mauvaise façon de coder car on fait Java en Go. Si on devait écrire une telle chose en Go, cela serait probablement organisé très différemment de ce.

12
hasen

L'incorporation fournit une délégation automatique. Cela en soi n'est pas suffisant pour remplacer l'héritage, car l'incorporation ne fournit aucune forme de polymorphisme. Les interfaces Go fournissent un polymorphisme, elles sont un peu différentes des interfaces que vous pouvez utiliser (certaines personnes les comparent au typage canard ou typage structurel).

Dans d'autres langues, les hiérarchies d'héritage doivent être soigneusement conçues car les changements sont larges et donc difficiles à faire. Go évite ces pièges tout en offrant une alternative puissante.

Voici un article qui plonge dans OOP avec Go un peu plus: http://nathany.com/good

7
nathany

Les gens ont demandé des liens vers des informations sur l'intégration dans Go.

Voici un document "Go efficace" où l'intégration est discutée et où des exemples concrets sont fournis.

http://golang.org/doc/effective_go.html#embedding

L'exemple a plus de sens lorsque vous avez déjà une bonne compréhension des interfaces et des types Go, mais vous pouvez le simuler en pensant à une interface comme un nom pour un ensemble de méthodes et si vous pensez à une structure similaire à une structure C.

Pour plus d'informations sur les structures, vous pouvez voir la spécification du langage Go, qui mentionne explicitement les membres sans nom des structures en tant que types intégrés:

http://golang.org/ref/spec#Struct_types

Jusqu'à présent, je ne l'ai utilisé que comme un moyen pratique de mettre une structure dans une autre sans avoir à utiliser un nom de champ pour la structure interne, lorsqu'un nom de champ n'ajoutait aucune valeur au code source. Dans l'exercice de programmation ci-dessous, je regroupe un type de proposition dans un type qui a une proposition et un canal de réponse.

https://github.com/ecashin/go-getting/blob/master/bpaxos.go#L

3
Ed Cashin

Je l'aime.

Le langage que vous utilisez affecte vos schémas de pensée. (Demandez simplement à un programmeur C d'implémenter "Word count". Ils utiliseront probablement une liste chaînée, puis passeront à un arbre binaire pour les performances. Mais chaque programmeur Java/Ruby/Python utilisera un dictionnaire/hachage. Le langage a affecté leur cerveaux tellement qu'ils ne peuvent pas penser à utiliser une autre structure de données.)

Avec l'héritage, vous devez construire vers le bas - commencer par la chose abstraite, puis la sous-classer aux détails. Votre code utile réel sera enterré dans un niveau de classe N en profondeur. Cela rend difficile l'utilisation d'une "partie" d'un objet, car vous ne pouvez pas réutiliser le code sans le faire glisser dans les classes parentes.

Dans Go, vous pouvez "modéliser" vos classes de cette façon (avec des interfaces). Mais vous ne pouvez pas (ne pouvez pas) coder de cette façon.

Au lieu de cela, vous pouvez utiliser l'incorporation. Votre code peut être décomposé en petits modules isolés, chacun avec ses propres données. Cela rend la réutilisation triviale. Cette modularité a peu à voir avec vos "gros" objets. (c'est-à-dire que dans Go, vous pouvez écrire une méthode "quack ()" qui ne connaît même pas votre classe Duck. Mais dans un langage typique OOP, vous ne pouvez pas déclarer "my Duck") L'implémentation de .quack () ne dépend d'aucune autre méthode de Duck. ")

Dans Go, cela oblige constamment le programmeur à penser à la modularité. Cela conduit à des programmes qui ont un faible couplage. Un faible couplage facilite la maintenance. ("oh, regardez, Duck.quack () est vraiment long et complexe, mais au moins je sais que cela ne dépend pas du reste de Duck.")

3
BraveNewCurrency

Je suis en train d'apprendre sur Go, mais comme vous demandez un avis, je vais vous en proposer un basé sur ce que je sais jusqu'à présent. L'incorporation semble être typique de beaucoup d'autres choses dans Go, qui est un support de langage explicite pour les meilleures pratiques qui sont déjà en cours dans les langues existantes. Par exemple, comme l'a noté Alex Martelli, le Gang of 4 dit "préfère la composition à l'héritage". Go supprime non seulement l'héritage, mais rend la composition plus facile et plus puissante qu'en C++/Java/C #.

J'ai été intrigué par des commentaires comme "Go n'apporte rien de nouveau que je ne peux pas déjà faire dans la langue X" et "pourquoi avons-nous besoin d'une autre langue?" Il me semble que dans un sens, Go ne fournit rien de nouveau qui ne pouvait être fait auparavant avec un certain travail, mais dans un autre sens, ce qui est nouveau, c'est que Go facilitera et encouragera l'utilisation des meilleures techniques qui sont déjà en pratique en utilisant d'autres langues.

3
Greg Graham