FAQ GoConsultez toutes les FAQ
Nombre d'auteurs : 6, nombre de questions : 45, dernière mise à jour : 20 août 2015 Ajouter une question
Cette FAQ a été réalisée essentiellement à partir de la traduction de la FAQ officielle GO.
Nous tenons à souligner que cette faq ne garantit en aucun cas que les informations qu'elle propose sont correctes. Les auteurs font leur maximum, mais l'erreur est humaine. Cette faq ne prétend pas non plus être complète. Si vous trouvez une erreur, ou que vous souhaitez nous aider en devenant rédacteur, lisez ceci.
Sur ce, nous vous souhaitons une bonne lecture.
L'équipe de rédaction Developpez.com
- 1- Est-ce que Go est un langage orienté objet ?
- 2-Comment puis-je obtenir le dispatch dynamique des méthodes ?
- 3-Pourquoi n'y a-t-il pas d'héritage de type ?
- 4- Pourquoi len est une fonction et non une méthode ?
- 5- Pourquoi Go ne supporte pas la surcharge des méthodes et des opérateurs ?
- 6- Pourquoi Go n'a pas la déclaration de type "implements"?
- 7- Comment garantir que mon type satisfait une interface ?
- 8- Pourquoi le type T ne satisfait-il pas l'interface Equal ?
- 9- Puis-je convertir []T vers un []interface{} ?
- 10- Pourquoi la valeur d'erreur nil n'est pas égale à nil ?
[Traduction de la FAQ officielle]
Oui et non, bien que Go possède des types et des méthodes, et autorise un style orienté objet, il n'y a pas de hiérarchie de types.
Le concept d'interface en Go propose une approche différente que nous pensons facile à utiliser et d'une certaine façon plus générale. Il y a aussi moyen d’imbriquer des types dans d'autres pour donner quelque chose d'analogue à l'héritage dans une hiérarchie de classes, mais avec des différences.
De plus, les méthodes en Go sont plus génériques que celles du C++ ou Java : elles peuvent être définies pour n'importe quel type de données, même les types « built-in » comme les entiers simples ou non bornés. Elles ne sont pas restreintes aux structures (classes).
En outre, l'absence de hiérarchisation des types fait que les objets en Go sont beaucoup plus légers que dans les langages comme C++ ou Java.
[Traduction de la FAQ officielle]
Le seul moyen d'avoir un dispatch dynamique des méthodes en Go est de passer par les interfaces.
Les méthodes sur les structures ou autres types concrets sont toujours résolues statiquement.
[Traduction de la FAQ officielle]
La programmation orientée objet, du moins pour les langages les plus connus, implique trop de complexité sur les relations entre les types, alors que ces relations pourraient souvent être dérivées automatiquement. Go adopte une approche différente.
Plutôt que d'exiger du programmeur de déclarer à l'avance la liaison entre deux types, dans Go, un type satisfait toute interface qui spécifie un sous-ensemble de ses méthodes. Non seulement cette approche réduit le besoin de comptabiliser et de suivre les relations entre entités, mais elle présente de réels avantages. Les types peuvent satisfaire à de nombreuses interfaces à la fois, en évitant la complexité de l'héritage multiple traditionnel.
Les interfaces peuvent être très légères (une interface avec une ou même zéro méthode peut exprimer un concept utile).
Les interfaces peuvent être ajoutées après coup si une nouvelle idée émerge ou pour les besoins de tests, sans modifier les types originaux. Parce qu'il n'y a pas de relation explicite entre les types et les interfaces, il n'y a pas besoin de s'occuper de la hiérarchie des types.
Il est possible d'utiliser ces idées pour construire quelque chose d'analogue aux pipes d'UNIX.
Par exemple, considérez comment fmt.Fprintf autorise l'affichage sur n'importe quelle sortie, pas juste un fichier, ou comment le paquetage bufio peut être complètement séparé des fichiers IO, ou comment le paquetage image génère des fichiers images. Toutes ces idées proviennent d'une seule et même interface (io.Writer) représentant une méthode simple (write). Nous ne faisons que gratter la surface. Les interfaces de Go ont profondément influencé la manière dont les programmes sont structurés.
Il faut un certain temps pour s'y habituer, mais ce style implicite de type de dépendance est l'un des aspects les plus productifs sur GO.
[Traduction de la FAQ officielle]
Nous avons débattu sur ce point et décidé qu'implémenter len et les fonctions similaires en tant que fonctions marchait très bien en pratique et ne compliquait pas la question des interfaces (dans le sens des types de Go) de types de base.
[Traduction de la FAQ officielle]
La détermination des méthodes se simplifie s'il n'y a pas besoin d'effectuer une correspondance de type. L'expérience d'autres langages nous apprend que disposer d'une variété de méthodes avec le même nom, mais des signatures différentes peut occasionnellement s'avérer utile, mais peut également être source de confusion et de fragilité dans les faits. Opter pour une correspondance uniquement par le nom et exiger la cohérence des types a permis une simplification majeure du système des types de Go.
[Traduction de la FAQ officielle]
Un type de Go satisfait une interface en implémentant les méthodes de cette interface, c'est tout. Cette propriété permet aux interfaces d'être définies et utilisées sans avoir à modifier le code existant. Elle permet une sorte de typage structurel qui favorise la séparation des problèmes et améliore la réutilisation du code, et elle facilite la construction sur les modèles qui émergent au fur et à mesure du développement. La sémantique des interfaces est l'une des principales raisons de la sensation de légèreté de Go.
Voir la question sur le type d'héritage pour plus de détails.
[Traduction de la FAQ officielle]
On peut demander au compilateur de vérifier si un type T implémente une interface I en essayant de faire une affectation :
Code Go : | Sélectionner tout |
1 2 | type T struct{} var _ I = T{} // Verifie que T implemente I |
Si T n’implémente pas I, le problème sera détecté lors de la compilation.
Si vous souhaitez que l'utilisateur d'une interface déclare explicitement qu'il l’implémente, vous pouvez ajouter une méthode avec un nom descriptif de l’ensemble des méthodes de l'interface. Par exemple :
Code Go : | Sélectionner tout |
1 2 3 4 | type Fooer interface { Foo() ImplementsFooer() } |
Un type doit implémenter la méthode ImplementsFooer pour être de type Fooer, clairement documenter la chose et l'annoncer dans la godoc.
Code Go : | Sélectionner tout |
1 2 3 | type Bar struct{} func (b Bar) ImplementsFooer() {} func (b Bar) Foo() {} |
La plupart des codes ne font pas usage de ces contraintes, car ils limitent l'utilité de l'idée de l'interface. Parfois, cependant, elles sont nécessaires pour résoudre les ambiguïtés entre des interfaces similaires.
[Traduction de la FAQ officielle]
Considérons cette simple interface représentant un objet qui peut être comparé avec une autre valeur :
Code Go : | Sélectionner tout |
1 2 3 | type Equaler interface { Equal(Equaler) bool } |
et le type, T :
Code Go : | Sélectionner tout |
1 2 | type T int func (t T) Equal(u T) bool { return t == u } // ne satisfait pas Equaler |
Différemment de la situation analogue dans d'autres systèmes de type polymorphique, T n'implémente pas Equaler. L'argument de type : T.Equal est T, et non littéralement le type requis par Equaler.
Dans Go, le type de système ne favorise pas l'argument de Equal; Il en advient de la responsabilité du programmeur, comme illustré dans le type T2, qui lui, implémente Equaler
Code Go : | Sélectionner tout |
1 2 | type T2 int func (t T2) Equal(u Equaler) bool { return t == u.(T2) } // satisfait Equaler |
Même si cela est différent des autres systèmes de type, mais, parce que dans Go n'importe quel type qui satisfait Equaler pourrait être passé comme argument à T2.Equal, nous devons vérifier que l'argument est de type T2 à l'éxecution. Certains langages s'arrangent pour garantir ce fait à la compilation.
Un autre exemple qui va dans l'autre sens :
Code Go : | Sélectionner tout |
1 2 3 4 5 | type Opener interface { Open() Reader } func (t T3) Open() *os.File |
Dans Go, T3 ne satisfait pas Opener, alors qu'il le pourrait dans d'autres langages.
S'il est vrai que le système de type de Go fait moins pour le programmeur dans de tels cas, l'absence de sous-typage rend les règles sur la satisfaction de l'interface très faciles à énoncer : est-ce que le nom et la signature de la fonction correspondent exactement avec celle de l'interface ? La règle de Go est également facile à mettre en œuvre efficacement. Nous estimons que ces prestations compensent le manque de promotion de types automatiques. Go devrait-il adopter un jour une forme de typage générique, nous espérons qu'il y aura un moyen d'exprimer l'idée de ces exemples, mais aussi de pouvoir les faire vérifier statistiquement.
[Traduction de la FAQ officielle]
Pas directement, parce qu'ils n'ont pas la même représentation en mémoire. Il est nécessaire de copier les éléments individuellement vers la tranche de destination. Cet exemple convertit une tranche d'int en une tranche d'interface{}
Code Go : | Sélectionner tout |
1 2 3 4 5 | t := []int{1, 2, 3, 4} s := make([]interface{}, len(t)) for i, v := range t { s[i] = v } |
[Traduction de la FAQ officielle]
Sous le capot, les interfaces sont mises en œuvre sous la forme de deux éléments, un type et une valeur. La valeur, appelée valeur dynamique de l'interface, est une valeur concrète arbitraire, et le type est le type de cette valeur. Pour un int de valeur 3, une valeur de l'interface contient schématiquement (int, 3).
Une valeur de l'interface est nil seulement si la valeur et le type internes sont à non définis (nil, nil). En particulier, une interface à nil aura toujours un type à nil. Si nous stockons un pointeur de type *int dans l'interface, le type contenu sera un *int indépendamment de la valeur du pointeur (*int, nil).
Une telle valeur de l'interface sera donc non-nil, même si le pointeur à l'intérieur est nil.
Cette situation peut être source de confusion, et se pose souvent quand une valeur nil est stockée à l'intérieur d'une valeur d'interface comme un retour d'erreur:
Code Go : | Sélectionner tout |
1 2 3 4 5 6 7 | func returnsError() error { var p *MyError = nil if bad() { p = ErrBad } return p // Va toujours retourner une erreur non nulle } |
Si tout va bien, la fonction retourne un nil p, donc, la valeur de retour est une interface d'erreur contenant (*MyError, nil). Cela signifie que si l'appelant compare l'erreur retournée à nil, on aura toujours l'impression qu'il y a eu une erreur, même si rien de fâcheux n'est arrivé. Pour retourner une nil error appropriée à l'appelant, la fonction doit retourner un nil explicite :
Code Go : | Sélectionner tout |
1 2 3 4 5 6 | func returnsError() error { if bad() { return ErrBad } return nil } |
Il est recommandé que les fonctions qui retournent des erreurs utilisent systématiquement le type error dans leur signature (comme nous l'avons fait ci-dessus) plutôt qu'un type concret comme *MyError, pour assurer que l'erreur est créée correctement. Par exemple, os.Open renvoie une erreur même si (si non-nil) ce sont toujours des types concrets de types *os.PathError.
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.