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.
Défi
FacileConstruisons 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
AudioPlayerque votre système moderne attend, avec une méthodePlay(track string) stringqui 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
VinylPlayeravec un champSpeedRPM(int) — elle possède une méthodeDropNeedle(record string, side string) stringqui renvoiePlaying [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.
- Structure
adapter.go: Créez votre adaptateur qui rend l'appareil hérité compatible.Construisez une structure
VinylAdapterqui enveloppe unVinylPlayeret implémente l'interfaceAudioPlayer. LorsquePlayest appelé, l'adaptateur doit appeler la méthodeDropNeedledu 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éthodePlayrenvoieStreaming: [track]
- Structure
main.go: Démontrez que les deux lecteurs fonctionnent via la même interface.Créez une fonction
PlayMusic(player AudioPlayer, track string) stringqui 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 fonctionPlayMusic, 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 RhapsodyVotre sortie devrait être :
Streaming: Bohemian RhapsodyEt étant donné :
vinyl
33
Abbey RoadVotre sortie devrait être :
Playing Abbey Road (A side) at 33rpmEt étant donné :
vinyl
45
Blue MondayVotre sortie devrait être :
Playing Blue Monday (A side) at 45rpmRemarquez 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
}
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
1Fondamentaux de la POO en Go
Fichiers externesEspace de travail et Modules GoPackages et ImportsNoms exportés vs non exportésIntroduction à la POO en GoLes Structs comme ClassesDéfinir des méthodes sur les StructsReceveurs par pointeur vs par valeurInitialisation de StructsFonctions constructeursRécapitulatif - Calculatrice simple4Interfaces
Introduction aux interfacesImplémentation impliciteL'interface comme contratInterface vide (any)Assertion de typeType SwitchComposition d'interfacesInterfaces Stringer & ErrorRécapitulatif - Calculateur de formes7Encapsulation
Champs exportés vs non exportésEncapsulation au niveau du packageMéthodes Getter & SetterMasquage d'informations en GoRécapitulatif - Dossiers étudiants10Génériques (Go 1.18+)
Introduction aux génériquesParamètres de typeContraintes de typeStructures génériquesContournement pour les méthodes génériquesRécapitulatif - Collections génériques13Patrons de conception, Partie 1
Introduction aux Design PatternsPatron SingletonPatron FactoryPatron Abstract FactoryPatron ObserverPatron Strategy2Exploration approfondie des Types et Structs
Types de base et compositesDéfinitions de types personnalisésTags de StructStructs anonymesStructs imbriquéesValeurs par défaut et Zero ValuesRécapitulatif - Carnet de contacts5Composition plutôt qu'héritage
Pourquoi Go n'a pas d'héritageLes bases de l'imbrication de structsPromotion de méthodesImbriquer plusieurs structsImbrication vs AgrégationMasquage de méthodes imbriquéesRécapitulatif - Hiérarchie des employés8Gestion des erreurs & POO
L'interface errorTypes d'erreurs personnalisésError Wrapping (fmt.Errorf)Erreurs sentinelleserrors.Is() et errors.As()Panic, Defer et RecoverRécapitulatif - File Parser11Bibliothèque standard et POO
io.Reader & io.WriterL'interface sort.InterfaceL'interface fmt.Stringerencoding/json avec les StructsL'interface http.HandlerRécapitulatif - Modèles d'API REST14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeMiddleware en tant que Décorateur3Pointeurs et Mémoire
Bases des pointeurs en GoPointeurs vers des structsPassage par valeur vs référenceLa fonction new()Garbage Collection en GoRécapitulatif - Constructeur de liste chaînée6Le polymorphisme en Go
Le polymorphisme via les interfacesLe Duck Typing en GoRègles de satisfaction des interfacesCollections polymorphesInjection de dépendancesRécapitulatif - Processeur de paiement9Concurrence et POO
Bases des GoroutinesChannels et CommunicationChannels avec buffer vs sans bufferL'instruction Selectsync.Mutex et sync.RWMutexsync.WaitGroupConception de structures Thread-SafeRécapitulatif - Worker Pool