Interfaces du langage Go

ci-dessous :

La conception des interfaces de Go est simple mais puissante, et constitue un outil important pour réaliser le polymorphisme et le découplage.

Les interfaces nous permettent de lier différents types à un ensemble de méthodes communes, ce qui permet le polymorphisme et une conception flexible.

Caractéristiques des interfaces

Implémentation implicite:

  • Go n'a pas de mot-clé pour déclarer explicitement qu'un type implémente une interface.
  • Tant qu'un type implémente toutes les méthodes requises par une interface, ce type est automatiquement considéré comme implémentant cette interface.

Variables de type interface:

  • Une variable d'interface peut stocker n'importe quelle valeur qui implémente l'interface.
  • Une variable d'interface se compose en fait de deux parties :
    • Type dynamique: stocke le type réel de la valeur.
    • Valeur dynamique: stocke la valeur concrète.

Interface de valeur zéro:

  • La valeur zéro d'une interface estnil。
  • La valeur d'une variable d'interface non initialisée estnil, et elle ne contient aucun type dynamique ni aucune valeur.

Interface vide:

  • Définie commeinterface{}, peut représenter n'importe quel type.

Utilisations courantes des interfaces

  1. PolymorphismeDes types différents qui implémentent la même interface obtiennent un comportement polymorphe.
  2. DécouplageDéfinir des dépendances via des interfaces réduit le couplage entre les modules.
  3. Généralisation: Utilisation de l'interface videinterface{}représente n'importe quel type.

Définition et implémentation d'interface

Les définitions d'interface utilisent le mot-cléinterface, qui contient des déclarations de méthodes.

Exemple

/* Définir l'interface */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}

/* Définir la structure */
type struct_name struct {
   /* variables */
}

/* Implémenter les méthodes de l'interface */
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* Implémentation de la méthode */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* Implémentation de la méthode*/
}

Définir une interface simple :

type Shape interface {
    Area() float64
    Perimeter() float64
}
  • Shapeest une interface qui définit deux méthodes :AreaetPerimeter。
  • Tout type qui implémente ces deux méthodes est considéré comme ayant implémenté l'interface.ShapeInterface.

Implémenter l'interface :Les types implémentent une interface en implémentant toutes les méthodes requises par l'interface.

Exemple

package main

import (
        "fmt"
        "math"
)

// Définir l'interface
type Shape interface {
        Area() float64
        Perimeter() float64
}

// Définir une structure
type Circle struct {
        Radius float64
}

// Circle implémente l'interface Shape
func (c Circle) Area() float64 {
        return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
        return 2 * math.Pi * c.Radius
}

func main() {
        c := Circle{Radius: 5}
        var s Shape = c // Les variables d'interface peuvent stocker des types qui implémentent l'interface
        fmt.Println("Area:", s.Area())
        fmt.Println("Perimeter:", s.Perimeter())
}

Exécutez le code ci-dessus, la sortie est :

Area: 78.53981633974483
Perimeter: 31.41592653589793

Interface vide

Interface videinterface{}Il s'agit d'une interface spéciale en Go, représentant le sur-ensemble de tous les types.

  • Tout type implémente l'interface vide.
  • Il est souvent utilisé dans des scénarios qui nécessitent de stocker des données de tout type, tels que les conteneurs génériques, les paramètres génériques, etc.

Exemple

package main

import "fmt"

func printValue(val interface{}) {
        fmt.Printf("Value: %v, Type: %T\n", val, val)
}

func main() {
        printValue(42)         // int
        printValue("hello")    // string
        printValue(3.14)       // float64
        printValue([]int{1, 2}) // slice
}

Exécutez le code ci-dessus, la sortie est :

Value: 42, Type: int
Value: hello, Type: string
Value: 3.14, Type: float64
Value: [1 2], Type: []int

Assertion de type

L'assertion de type est utilisée pour extraire la valeur sous-jacente d'un type d'interface.

Syntaxe de base :

value := iface.(Type)
  • ifaceest une variable d'interface.
  • Typeest le type concret à asserter.
  • Si le type ne correspond pas, cela déclenchepanic。

Exemple

package main

import "fmt"

func main() {
        var i interface{} = "hello"
        str := i.(string) // Assertion de type
        fmt.Println(str)  // Sortie : hello
}

assertion de type avec vérification

Pour éviter un panic, une assertion de type vérifiée peut être utilisée :

value, ok := iface.(Type)
  • okIl s'agit d'une valeur booléenne indiquant si l'assertion a réussi.
  • Si l'assertion échoue,valueest la valeur zéro,okestfalse。

Exemple

package main

import "fmt"

func main() {
        var i interface{} = 42
        if str, ok := i.(string); ok {
                fmt.Println("Chaîne :", str)
        } else {
                fmt.Println("Pas une chaîne")
        }
}

Exécutez le code ci-dessus, la sortie est :

Not a string

switch de type

Le switch de type est une construction syntaxique en Go utilisée pour exécuter une logique différente en fonction du type concret d'une variable d'interface.

Exemple

package main

import "fmt"

func printType(val interface{}) {
        switch v := val.(type) {
        case int:
                fmt.Println("Entier :", v)
        case string:
                fmt.Println("Chaîne :", v)
        case float64:
                fmt.Println("Float:", v)
        default:
                fmt.Println("Unknown type")
        }
}

func main() {
        printType(42)
        printType("hello")
        printType(3.14)
        printType([]int{1, 2, 3})
}

Exécutez le code ci-dessus, la sortie est :

Integer: 42
String: hello
Float: 3.14
Unknown type

Composition d'interfaces

Les interfaces peuvent décrire des comportements plus complexes par l'imbrication et la composition.

Exemple

package main

import "fmt"

type Reader interface {
        Read() string
}

type Writer interface {
        Write(data string)
}

type ReadWriter interface {
        Reader
        Writer
}

type File struct{}

func (f File) Read() string {
        return "Lecture des données"
}

func (f File) Write(data string) {
        fmt.Println("Écriture des données :", data)
}

func main() {
        var rw ReadWriter = File{}
        fmt.Println(rw.Read())
        rw.Write("Bonjour, Go !")
}

valeur dynamique et type dynamique

Une variable d'interface contient en réalité deux parties :

  1. Type dynamique: le type concret stocké dans la variable d'interface.
  2. Valeur dynamique: la valeur du type concret.

Exemple de valeur dynamique et de type dynamique :

Exemple

package main

import "fmt"

func main() {
        var i interface{} = 42
        fmt.Printf("Type dynamique : %T, Valeur dynamique : %v\n", i, i)
}

Exécutez le code ci-dessus, la sortie est :

Dynamic type: int, Dynamic value: 42

Valeur zéro d'interface

La valeur zéro d'une interface est nil.

Lorsque le type dynamique et la valeur dynamique d'une variable d'interface sont tous deux nil, la variable d'interface est nil.

Exemple de valeur zéro d'interface :

Exemple

package main

import "fmt"

func main() {
        var i interface{}
        fmt.Println(i == nil) // Sortie : true
}

Exemples pratiques

Les deux exemples suivants démontrent l'utilisation des interfaces :

Exemple 1

package main

import (
    "fmt"
)

type Phone interface {
    call()
}

type NokiaPhone struct {
}

func (nokiaPhone NokiaPhone) call() {
    fmt.Println("Je suis Nokia, je peux vous appeler !")
}

type IPhone struct {
}

func (iPhone IPhone) call() {
    fmt.Println("Je suis iPhone, je peux vous appeler !")
}

func main() {
    var phone Phone

    phone = new(NokiaPhone)
    phone.call()

    phone = new(IPhone)
    phone.call()

}

Dans l'exemple ci-dessus, nous avons défini une interface.Phone, l'interface contient une méthodecall(). Ensuite, nousmainDans la fonction, une variable de typePhoneest définie, et des valeurs lui sont respectivement assignées commeNokiaPhoneetIPhone. Ensuite, appelez la méthodecall(), la sortie est la suivante :

I am Nokia, I can call you!
I am iPhone, I can call you!

Le deuxième exemple d'interface :

Exemple

package main

import "fmt"

type Shape interface {
    area() float64
}

type Rectangle struct {
    width  float64
    height float64
}

func (r Rectangle) area() float64 {
    return r.width * r.height
}

type Circle struct {
    radius float64
}

func (c Circle) area() float64 {
    return 3.14 * c.radius * c.radius
}

func main() {
    var s Shape

    s = Rectangle{width: 10, height: 5}
    fmt.Printf("Aire du rectangle : %f\n", s.area())

    s = Circle{radius: 3}
    fmt.Printf("Aire du cercle : %f\n", s.area())
}

Dans l'exemple ci-dessus, nous avons défini une interface Shape, qui définit une méthode area() qui retourne une valeur d'aire de type float64. Ensuite, nous avons défini deux structures, Rectangle et Circle, qui implémentent respectivement la méthode area() de l'interface Shape. Dans la fonction main(), nous avons d'abord défini une variable s de type Shape, puis nous lui avons respectivement assigné des instances de Rectangle et Circle, et via la méthode area() nous avons calculé leurs aires et les avons affichées. La sortie est la suivante :

矩形面积: 50.000000
圆形面积: 28.260000

Il convient de noter qu'une variable de type interface peut stocker une valeur de tout type qui implémente l'interface. Dans l'exemple, nous avons assigné des instances des deux types Rectangle et Circle à la variable s de type Shape, et appelé leurs méthodes de calcul d'aire via la méthode area().

autres extensions