Intégration à la place de l’inheritance dans Go

Quelle est votre opinion sur cette décision de conception? Quels avantages a-t-il et quels inconvénients?

Liens:

  • Description de l’incorporation

Dans un commentaire, vous vous êtes demandé si l’idée d’incorporation était suffisante pour «remplacer complètement l’inheritance». 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’inheritance. Snit est encore très différent de l’approche de Go, mais à cet égard, ils ont un terrain philosophique commun. C’est un mécanisme permettant de réunir des fonctionnalités et des responsabilités, et non une hiérarchie pour les classes.

Comme d’autres l’ont déclaré, il s’agit vraiment de savoir quel type de pratiques de programmation les concepteurs de langage veulent prendre en charge. Tous ces choix ont leurs propres avantages et inconvénients; Je ne pense pas que les «meilleures pratiques» sont une phrase qui s’applique nécessairement ici. Nous allons probablement voir quelqu’un développer une couche d’inheritance pour Go finalement.

(Pour tous les lecteurs familiers avec Tcl, je pensais que Snit était un peu plus proche de la “sensation” du langage que [incr Tcl] . Tcl concerne la délégation, du moins ma façon de penser.)

Le principe crucial du Gang of 4 est “préférer la composition à l’inheritance”; Va te faire suivre ;-).

Les seules utilisations réelles de l’inheritance sont les suivantes:

  • Polymorphisme

    • Le système de “typage de canard statique” de l’interface de Go résout ce problème
  • Implémentation d’emprunt d’une autre classe

    • C’est pour cela que l’intégration est pour

L’approche de Go ne correspond pas exactement à 1-to-1, considérez cet exemple classique d’inheritance et de polymorphism en Java ( basé sur ceci ):

 //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’inheritance et le polymorphism sont combinés, et vous ne pouvez pas traduire ceci en Go sans changer la structure sous-jacente.

Je n’ai pas plongé profondément dans Go, mais je suppose que cela ressemblerait à ceci:

 //roughly Go? .... no? //for illustrative purposes only; not likely to comstack // //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, il s'agit d'une mauvaise façon de coder, car on fait Java dans Go. Si on devait écrire une telle chose dans Go, cela serait probablement très différent de cela.

L’incorporation fournit une délégation automatique. Cela en soi ne suffit pas à remplacer l’inheritance, car l’intégration ne fournit aucune forme de polymorphism. Les interfaces Go fournissent un polymorphism, elles sont un peu différentes des interfaces que vous pouvez utiliser (certaines personnes les assimilent à un typage ou à un typage structurel).

Dans d’autres langages, les hiérarchies d’inheritance doivent être conçues avec soin, car les changements sont vastes et donc difficiles à réaliser. Go évite ces pièges tout en offrant une alternative puissante.

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

Je ne fais que commencer à apprendre à propos de Go, mais comme vous demandez un avis, j’en offrirai un basé sur ce que je sais jusqu’ici. L’incorporation semble être typique de beaucoup d’autres choses dans Go, qui est un support de langage explicite pour les meilleures pratiques déjà utilisées dans les langages existants. Par exemple, comme l’a noté Alex Martelli, le Gang of 4 indique «préférer la composition à l’inheritance». Go ne supprime pas seulement l’inheritance, mais rend la composition plus facile et plus puissante qu’en C ++ / Java / C #.

J’ai été insortinggué par des commentaires comme “Go ne fournit rien de nouveau que je ne peux pas déjà faire dans le langage 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 pourrait être fait auparavant avec certains travaux, mais dans un autre sens, ce qui est nouveau, c’est que Go facilitera et encouragera l’utilisation des meilleures techniques déjà en pratique en utilisant d’autres langues.

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

Voici un document “Effective Go” où l’on discute de l’intégration et où des exemples concrets sont fournis.

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

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

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

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

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

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

J’aime ça.

Le langage que vous utilisez affecte vos modes de pensée. (Demandez simplement à un programmeur C d’implémenter le “nombre de mots”. Ils utiliseront probablement une liste liée, puis passeront à un arbre binary pour les performances. Mais chaque programmeur Java / Ruby / Python utilisera un dictionnaire / hachage. des cerveaux tellement qu’ils ne peuvent penser à utiliser une autre structure de données.)

Avec l’inheritance, vous devez construire – commencer par l’abstrait, puis le sous-classer dans les détails. Votre code utile réel sera enterré dans une classe de niveaux N profond. Cela rend difficile l’utilisation d’une “partie” d’un object, car vous ne pouvez pas réutiliser le code sans faire glisser les classes parentes.

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

Au lieu de cela, vous pouvez utiliser l’intégration. Votre code peut être divisé en petits modules isolés, chacun avec ses propres données. Cela rend la réutilisation sortingviale. Cette modularité a peu à voir avec vos “gros” objects. (c.-à-d. Dans Go, vous pouvez écrire une méthode “quack ()” qui ne connaît même pas votre classe Duck. Mais dans un langage OOP typique, vous ne pouvez pas déclarer que “mon implémentation Duck.quack () ne dépend pas toute autre méthode de canard. “)

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