Menu
Coddy logo textTech

Règles de satisfaction des interfaces

Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy. Leçon 43 sur 107.

Bien que Go utilise le typage structurel pour la satisfaction implicite des interfaces, des règles spécifiques déterminent si un type satisfait réellement une interface. Comprendre ces règles vous aide à éviter des bugs subtils.

La règle la plus importante concerne les récepteurs pointeur. Si une méthode est définie avec un récepteur pointeur, seul un pointeur vers ce type satisfait à l’interface, et non la valeur elle-même :

type Saver interface {
    Save() string
}

type Document struct{ Name string }

func (d *Document) Save() string {  // récepteur pointeur
    return "Saved: " + d.Name
}

func Process(s Saver) {
    fmt.Println(s.Save())
}

Parce que Save() possède un récepteur pointeur, seul *Document satisfait Saver :

func main() {
    doc := Document{Name: "report.txt"}
    
    Process(&doc)  // fonctionne - le pointeur satisfait l'interface
    // Process(doc)  // erreur de compilation - la valeur ne satisfait pas l'interface
}

Cependant, l’inverse est plus flexible. Si une méthode possède un récepteur de valeur, les deux types, valeur et pointeur, satisfont à l’interface. Go déréférence automatiquement les pointeurs lors de l’appel de méthodes avec un récepteur de valeur :

func (d Document) Info() string {  // récepteur par valeur
    return d.Name
}

// Document et *Document satisfont tous deux une interface exigeant Info()

Cette asymétrie existe parce que Go peut toujours obtenir une valeur à partir d’un pointeur (en le déréférençant), mais ne peut pas toujours obtenir un pointeur à partir d’une valeur (la valeur pourrait ne pas être adressable). Garder cette règle à l’esprit permet d’éviter des erreurs de compilation déroutantes lorsque vous travaillez avec des interfaces.

challenge icon

Défi

Facile

Construisons un système de configuration qui montre comment les récepteurs de pointeur et de valeur affectent la satisfaction d’une interface. Vous créerez des types pour lesquels le choix du récepteur détermine si des valeurs, des pointeurs ou les deux peuvent être utilisés avec une interface.

Vous organiserez votre code sur trois fichiers :

  • config.go : définissez une interface Configurable qui requiert deux méthodes : GetValue() string et SetValue(string). Créez ensuite deux types de configuration :
    • ReadOnlyConfig avec un champ Value : utilisez un récepteur de valeur pour GetValue() (renvoie la valeur de Value) et un récepteur de pointeur pour SetValue() (met à jour la valeur de Value)
    • Setting avec un champ Data : utilisez des récepteurs de valeur pour les deux méthodes (GetValue renvoie Data, SetValue affiche simplement "Cannot modify" sans rien modifier)
  • processor.go : créez une fonction appelée ProcessConfig qui accepte une Configurable et une nouvelle chaîne de valeur. Elle doit afficher la valeur actuelle avec GetValue(), appeler SetValue() avec la nouvelle valeur, puis afficher à nouveau la valeur pour montrer les éventuelles modifications.
  • main.go : lisez les détails de configuration depuis l’entrée et montrez les règles de satisfaction de l’interface :
    1. Créez un ReadOnlyConfig et transmettez un pointeur à ProcessConfig (obligatoire, car SetValue possède un récepteur de pointeur)
    2. Créez un Setting et transmettez directement la valeur à ProcessConfig (cela fonctionne, car les deux méthodes possèdent des récepteurs de valeur)

Les entrées suivantes seront fournies :

  • Ligne 1 : valeur initiale pour ReadOnlyConfig
  • Ligne 2 : nouvelle valeur à définir pour ReadOnlyConfig
  • Ligne 3 : valeur initiale pour Setting
  • Ligne 4 : nouvelle valeur à tenter pour Setting

Votre fonction ProcessConfig doit afficher dans ce format :

Current: [value]
Current: [value after SetValue]

Par exemple, avec debug, production, localhost et remote, votre sortie doit être :

Current: debug
Current: production
Current: localhost
Cannot modify
Current: localhost

Remarquez que ReadOnlyConfig modifie effectivement sa valeur (car nous avons transmis un pointeur), tandis que Setting reste inchangé (son SetValue avec un récepteur de valeur ne peut pas modifier l’original). L’idée essentielle est que les valeurs de ReadOnlyConfig seules ne satisferaient pas Configurable : seuls les pointeurs le peuvent, tandis que les valeurs de Setting fonctionnent directement, car toutes ses méthodes utilisent des récepteurs de valeur.

Essayez vous-même

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Lire les entrées
	scanner.Scan()
	rocInitial := scanner.Text()
	scanner.Scan()
	rocNew := scanner.Text()
	scanner.Scan()
	settingInitial := scanner.Text()
	scanner.Scan()
	settingNew := scanner.Text()
	
	// TODO: Créer un ReadOnlyConfig avec la valeur rocInitial
	// TODO: Passer un POINTEUR à ProcessConfig (requis car SetValue a un récepteur pointeur)
	
	// TODO: Créer un Setting avec la valeur settingInitial
	// TODO: Passer la VALEUR directement à ProcessConfig (fonctionne car les deux méthodes ont des récepteurs par valeur)
	
	// Utiliser ces variables pour éviter les erreurs de variables non utilisées
	_ = rocInitial
	_ = rocNew
	_ = settingInitial
	_ = settingNew
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation Orientée Objet

Entraînez-vous par vous-même : Compilateur Go en ligne