Menu
Coddy logo textTech

Patron Adaptateur

Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 92 sur 107.

Le pattern Adapter permet à des interfaces incompatibles de fonctionner ensemble en enveloppant un type existant avec une nouvelle interface. Tandis que Command encapsule des actions, Adapter traduit une interface en une autre que les clients attendent.

Imaginez que vous ayez une ancienne imprimante qui utilise une signature de méthode différente de celle attendue par votre application :

// Interface cible utilisée par votre application
type Printer interface {
    Print(text string) string
}

// Ancienne imprimante avec une interface incompatible
type OldPrinter struct{}

func (o OldPrinter) PrintDocument(doc string, copies int) string {
    return fmt.Sprintf("Old printer: %s (x%d)", doc, copies)
}

L'adaptateur enveloppe le type hérité et implémente l'interface attendue :

type OldPrinterAdapter struct {
    oldPrinter OldPrinter
}

func (a OldPrinterAdapter) Print(text string) string {
    return a.oldPrinter.PrintDocument(text, 1)
}

Désormais, l'imprimante héritée fonctionne parfaitement avec le code attendant l'interface Printer :

func PrintMessage(p Printer, msg string) string {
    return p.Print(msg)
}

adapter := OldPrinterAdapter{oldPrinter: OldPrinter{}}
result := PrintMessage(adapter, "Hello")  // "Old printer: Hello (x1)"

Le pattern Adapter est inestimable lors de l'intégration de bibliothèques tierces, du travail avec du code hérité, ou lorsque vous devez utiliser des classes existantes qui ne correspondent pas aux exigences de votre interface. Il agit comme un pont sans modifier le code original.

challenge icon

Défi

Facile

Construisons un système de lecteur multimédia en utilisant le motif Adapter ! Vous disposez d'un lecteur audio moderne qui attend une interface unifiée, mais vous devez intégrer un équipement audio hérité qui utilise des signatures de méthode complètement différentes. Votre adaptateur comblera cet écart, permettant à l'ancien matériel de fonctionner de manière transparente avec le nouveau système.

Vous organiserez votre code sur trois fichiers :

  • player.go : Définissez votre interface cible et un appareil audio hérité.

    Créez une interface AudioPlayer que votre système moderne attend, avec une méthode Play(track string) string qui prend un nom de piste et renvoie un message de lecture.

    Définissez également un appareil hérité qui ne correspond pas à cette interface :

    • Structure VinylPlayer avec un champ SpeedRPM (int) — elle possède une méthode DropNeedle(record string, side string) string qui renvoie Playing [record] ([side] side) at [speed]rpm

    L'interface du VinylPlayer est incompatible avec AudioPlayer — elle nécessite deux paramètres et utilise un nom de méthode complètement différent.

  • adapter.go : Créez votre adaptateur qui rend l'appareil hérité compatible.

    Construisez une structure VinylAdapter qui enveloppe un VinylPlayer et implémente l'interface AudioPlayer. Lorsque Play est appelé, l'adaptateur doit appeler la méthode DropNeedle du lecteur vinyle, en utilisant par défaut "A" pour le paramètre side.

    Créez également un lecteur moderne pour comparaison :

    • Structure DigitalPlayer — sa méthode Play renvoie Streaming: [track]
  • main.go : Démontrez que les deux lecteurs fonctionnent via la même interface.

    Créez une fonction PlayMusic(player AudioPlayer, track string) string qui utilise l'interface pour jouer de la musique — cette fonction ne doit pas savoir ni se soucier si elle utilise un lecteur numérique ou un lecteur vinyle adapté.

    Lisez un type de lecteur ("digital" ou "vinyl"), et s'il s'agit d'un vinyle, lisez également la vitesse RPM. Ensuite, lisez le nom de la piste. Créez le lecteur approprié (en utilisant l'adaptateur pour le vinyle), jouez la piste via votre fonction PlayMusic, et affichez le résultat.

Les entrées suivantes seront fournies :

  • Ligne 1 : Type de lecteur ("digital" ou "vinyl")
  • Ligne 2 : Vitesse RPM (uniquement si vinyle)
  • Dernière ligne : Nom de la piste à jouer

Par exemple, étant donné :

digital
Bohemian Rhapsody

Votre sortie devrait être :

Streaming: Bohemian Rhapsody

Et étant donné :

vinyl
33
Abbey Road

Votre sortie devrait être :

Playing Abbey Road (A side) at 33rpm

Et étant donné :

vinyl
45
Blue Monday

Votre sortie devrait être :

Playing Blue Monday (A side) at 45rpm

Remarquez comment la fonction PlayMusic fonctionne de manière identique avec les deux lecteurs — elle n'a aucune idée que le lecteur vinyle est en réalité adapté d'une interface complètement différente. L'adaptateur traduit les appels de l'interface moderne vers le format hérité en coulisses !

Aide-mémoire

Le pattern Adapter (ou Adaptateur) permet à des interfaces incompatibles de fonctionner ensemble en enveloppant un type existant avec une nouvelle interface.

Définissez une interface cible attendue par votre application :

type Printer interface {
    Print(text string) string
}

Un type hérité (legacy) avec une interface incompatible :

type OldPrinter struct{}

func (o OldPrinter) PrintDocument(doc string, copies int) string {
    return fmt.Sprintf("Old printer: %s (x%d)", doc, copies)
}

Créez un adaptateur qui enveloppe le type hérité et implémente l'interface cible :

type OldPrinterAdapter struct {
    oldPrinter OldPrinter
}

func (a OldPrinterAdapter) Print(text string) string {
    return a.oldPrinter.PrintDocument(text, 1)
}

Utilisez l'adaptateur pour faire fonctionner le type hérité avec le code attendant l'interface cible :

func PrintMessage(p Printer, msg string) string {
    return p.Print(msg)
}

adapter := OldPrinterAdapter{oldPrinter: OldPrinter{}}
result := PrintMessage(adapter, "Hello")  // "Old printer: Hello (x1)"

Le pattern Adapter est utile pour intégrer des bibliothèques tierces, travailler avec du code hérité ou adapter des classes existantes pour qu'elles correspondent aux interfaces requises sans modifier le code d'origine.

Essayez vous-même

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

// PlayMusic utilise l'interface AudioPlayer pour jouer de la musique
// Cette fonction fonctionne avec n'importe quelle implémentation de AudioPlayer
// TODO: Implémenter cette fonction pour appeler player.Play(track) et retourner le résultat
func PlayMusic(player AudioPlayer, track string) string {
	// TODO: Implémenter cette fonction
	return ""
}

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Lire le type de lecteur
	playerType, _ := reader.ReadString('\n')
	playerType = strings.TrimSpace(playerType)
	
	var player AudioPlayer
	var track string
	
	if playerType == "vinyl" {
		// Lire la vitesse RPM pour le vinyle
		rpmStr, _ := reader.ReadString('\n')
		rpmStr = strings.TrimSpace(rpmStr)
		rpm, _ := strconv.Atoi(rpmStr)
		
		// Lire le nom de la piste
		track, _ = reader.ReadString('\n')
		track = strings.TrimSpace(track)
		
		// TODO: Créer un VinylPlayer avec le RPM donné
		// TODO: L'envelopper dans un VinylAdapter
		// TODO: L'assigner à la variable player
		_ = rpm // Supprimer cette ligne lorsque vous utilisez rpm
	} else {
		// Lire le nom de la piste
		track, _ = reader.ReadString('\n')
		track = strings.TrimSpace(track)
		
		// TODO: Créer un DigitalPlayer
		// TODO: L'assigner à la variable player
	}
	
	// TODO: Appeler PlayMusic et afficher le résultat
	_ = player // Supprimer cette ligne lorsque vous utilisez player
	_ = track  // Supprimer cette ligne lorsque vous utilisez track
}
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