Aller dans les champs d’interface

Je connais le fait que, dans Go, les interfaces définissent la fonctionnalité plutôt que les données. Vous mettez un ensemble de méthodes dans une interface, mais vous ne pouvez pas spécifier de champs qui seraient requirejs sur tout ce qui implémente cette interface.

Par exemple:

// Interface type Giver interface { Give() int64 } // One implementation type FiveGiver struct {} func (fg *FiveGiver) Give() int64 { return 5 } // Another implementation type VarGiver struct { number int64 } func (vg *VarGiver) Give() int64 { return vg.number } 

Maintenant, nous pouvons utiliser l’interface et ses implémentations:

 // A function that uses the interface func GetSomething(aGiver Giver) { fmt.Println("The Giver gives: ", aGiver.Give()) } // Bring it all together func main() { fg := &FiveGiver{} vg := &VarGiver{3} GetSomething(fg) GetSomething(vg) } /* Resulting output: 5 3 */ 

Maintenant, ce que vous ne pouvez pas faire, c’est quelque chose comme ceci:

 type Person interface { Name ssortingng Age int64 } type Bob struct implements Person { // Not Go syntax! ... } func PrintName(aPerson Person) { fmt.Println("Person's name is: ", aPerson.Name) } func main() { b := &Bob{"Bob", 23} PrintName(b) } 

Cependant, après avoir joué avec des interfaces et des structures intégrées, j’ai découvert un moyen de le faire, après un certain temps:

 type PersonProvider interface { GetPerson() *Person } type Person struct { Name ssortingng Age int64 } func (p *Person) GetPerson() *Person { return p } type Bob struct { FavoriteNumber int64 Person } 

À cause de la structure incorporée, Bob a tout ce que Person a. Il implémente également l’interface PersonProvider, de sorte que nous pouvons passer Bob dans des fonctions conçues pour utiliser cette interface.

 func DoBirthday(pp PersonProvider) { pers := pp.GetPerson() pers.Age += 1 } func SayHi(pp PersonProvider) { fmt.Printf("Hello, %v!\r", pp.GetPerson().Name) } func main() { b := &Bob{ 5, Person{"Bob", 23}, } DoBirthday(b) SayHi(b) fmt.Printf("You're %v years old now!", b.Age) } 

Voici un terrain de jeu Go qui montre le code ci-dessus.

En utilisant cette méthode, je peux créer une interface qui définit des données plutôt que des comportements, et qui peuvent être implémentées par n’importe quelle structure en intégrant ces données. Vous pouvez définir des fonctions qui interagissent explicitement avec ces données incorporées et ne connaissent pas la nature de la structure externe. Et tout est vérifié au moment de la compilation! (La seule façon de gâcher, que je peux voir, serait d’intégrer l’interface PersonProvider dans Bob , plutôt qu’une Person concrète. Elle comstackrait et échouerait à l’exécution.)

Maintenant, voici ma question: est-ce une astuce, ou devrais-je le faire différemment?

C’est vraiment une astuce, et ça marche aussi longtemps que vous êtes cool, donnant access à ces champs dans le cadre de votre API. L’alternative que j’envisagerais est de conserver l’installation intégrable struct / interface , mais en définissant l’interface en termes de getters et de setters.

Le fait de masquer les propriétés derrière les getters et les setters vous donne une flexibilité supplémentaire pour effectuer ultérieurement des modifications rétrocompatibles. Supposons que vous souhaitiez un jour changer Person pour stocker non seulement un seul champ “Nom” mais le premier / milieu / dernier / préfixe; Si vous avez les méthodes Name() ssortingng et SetName(ssortingng) , vous pouvez satisfaire les utilisateurs existants de l’interface Person tout en ajoutant de nouvelles méthodes plus fines. Vous pouvez également vouloir marquer un object soutenu par une firebase database comme étant “sale” lorsqu’il comporte des modifications non enregistrées. vous pouvez le faire lorsque les mises à jour de données passent toutes par les méthodes SetFoo() .

Donc: avec getters / setters, vous pouvez changer les champs de structure tout en maintenant une API compatible, et append de la logique autour de la propriété get / sets car personne ne peut simplement faire p.Name = "bob" sans passer par votre code.

Cette flexibilité est plus pertinente lorsque votre type fait quelque chose de plus compliqué. Si vous avez une collection PersonCollection , celle-ci peut être sauvegardée en interne par un sql.Rows , une []*Person , une []uint d’identifiants de firebase database, etc. En utilisant la bonne interface, vous pouvez empêcher les appelants de se préoccuper de la manière dont io.Reader permet aux connexions réseau et aux fichiers de se ressembler.

Une chose spécifique: les interface dans Go ont la propriété particulière que vous pouvez en implémenter sans importer le paquet qui le définit; cela peut vous aider à éviter les importations cycliques . Si votre interface retourne une *Person , au lieu de simplement des chaînes ou autres, tous les PersonProviders doivent importer le paquet où Person est défini. Cela peut être bien ou même inévitable. c’est juste une conséquence à connaître.

Cela dit, il n’y a pas de convention Go pour cacher toutes vos données. (C’est une différence bienvenue de, disons, C ++.) Le stdlib fait des choses comme vous permet d’initialiser un http.Server avec votre configuration et vous promet qu’un bytes.Buffer est utilisable. C’est bien de faire vos propres trucs comme ça, et, en effet, je ne pense pas que vous ayez à faire une abstraction prématurée si la version plus concrète fonctionne. Il s’agit simplement d’être au courant des compromis.