Pattern Adapter
Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 92 di 107.
Il pattern Adapter permette a interfacce incompatibili di collaborare avvolgendo un tipo esistente con una nuova interfaccia. Mentre Command incapsula le azioni, Adapter traduce un'interfaccia in un'altra, come si aspettano i client.
Immagina di avere una stampante legacy che utilizza una firma di metodo diversa da quella prevista dalla tua applicazione:
// Interfaccia di destinazione utilizzata dalla tua applicazione
type Printer interface {
Print(text string) string
}
// Stampante legacy con un'interfaccia incompatibile
type OldPrinter struct{}
func (o OldPrinter) PrintDocument(doc string, copies int) string {
return fmt.Sprintf("Old printer: %s (x%d)", doc, copies)
}L’adattatore avvolge il tipo legacy e implementa l’interfaccia prevista:
type OldPrinterAdapter struct {
oldPrinter OldPrinter
}
func (a OldPrinterAdapter) Print(text string) string {
return a.oldPrinter.PrintDocument(text, 1)
}Ora la stampante legacy funziona senza problemi con il codice che si aspetta l'interfaccia Printer:
func PrintMessage(p Printer, msg string) string {
return p.Print(msg)
}
adapter := OldPrinterAdapter{oldPrinter: OldPrinter{}}
result := PrintMessage(adapter, "Hello") // "Old printer: Hello (x1)"Il pattern Adapter è prezioso quando integri librerie di terze parti, lavori con codice legacy o hai bisogno di usare classi esistenti che non corrispondono ai requisiti della tua interfaccia. Funge da ponte senza modificare il codice originale.
Sfida
FacileCostruiamo un sistema di riproduzione multimediale usando il pattern Adapter! Hai un lettore audio moderno che si aspetta un'interfaccia unificata, ma devi integrare apparecchiature audio legacy che utilizzano firme dei metodi completamente diverse. Il tuo adapter colmerà questa lacuna, permettendo al vecchio hardware di funzionare senza problemi con il nuovo sistema.
Organizzerai il codice in tre file:
player.go: definisci l'interfaccia di destinazione e un dispositivo audio legacy.Crea un'interfaccia
AudioPlayerche il tuo sistema moderno si aspetta, con un metodoPlay(track string) stringche accetta il nome di una traccia e restituisce un messaggio di riproduzione.Definisci anche un dispositivo legacy che non corrisponde a questa interfaccia:
- struct
VinylPlayercon un campoSpeedRPM(int). Ha un metodoDropNeedle(record string, side string) stringche restituiscePlaying [record] ([side] side) at [speed]rpm
L'interfaccia di VinylPlayer è incompatibile con AudioPlayer. Richiede due parametri e usa un nome di metodo completamente diverso.
- struct
adapter.go: crea il tuo adapter per rendere compatibile il dispositivo legacy.Crea uno struct
VinylAdapterche racchiude unVinylPlayere implementa l'interfacciaAudioPlayer. Quando viene chiamatoPlay, l'adapter dovrebbe chiamare il metodoDropNeedledel lettore vinile, usandoAcome valore predefinito per il parametro side.Crea anche un lettore moderno per fare un confronto:
- struct
DigitalPlayer: il suo metodoPlayrestituisceStreaming: [track]
- struct
main.go: dimostra che entrambi i lettori funzionano tramite la stessa interfaccia.Crea una funzione
PlayMusic(player AudioPlayer, track string) stringche usa l'interfaccia per riprodurre musica. Questa funzione non dovrebbe sapere né preoccuparsi se sta usando un lettore digitale o un lettore vinile adattato.Leggi il tipo di lettore (
digitalovinyl) e, se è vinile, leggi anche la velocità RPM. Poi leggi il nome della traccia. Crea il lettore appropriato (usando l'adapter per il vinile), riproduci la traccia tramite la funzionePlayMusice stampa il risultato.
Verranno forniti i seguenti input:
- Riga 1: tipo di lettore (
digitalovinyl) - Riga 2: velocità RPM (solo se è vinile)
- Ultima riga: nome della traccia da riprodurre
Ad esempio, dato:
digital
Bohemian RhapsodyL'output dovrebbe essere:
Streaming: Bohemian RhapsodyE dato:
vinyl
33
Abbey RoadL'output dovrebbe essere:
Playing Abbey Road (A side) at 33rpmE dato:
vinyl
45
Blue MondayL'output dovrebbe essere:
Playing Blue Monday (A side) at 45rpmNota come la funzione PlayMusic funzioni allo stesso modo con entrambi i lettori. Non sa che il lettore vinile viene adattato da un'interfaccia completamente diversa. L'adapter traduce dietro le quinte le chiamate all'interfaccia moderna nel formato legacy!
Provalo tu
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// PlayMusic usa l'interfaccia AudioPlayer per riprodurre musica
// Questa funzione funziona con qualsiasi implementazione di AudioPlayer
// TODO: Implementa questa funzione in modo che chiami player.Play(track) e restituisca il risultato
func PlayMusic(player AudioPlayer, track string) string {
// TODO: Implementa questa funzione
return ""
}
func main() {
reader := bufio.NewReader(os.Stdin)
// Leggi il tipo di lettore
playerType, _ := reader.ReadString('\n')
playerType = strings.TrimSpace(playerType)
var player AudioPlayer
var track string
if playerType == "vinyl" {
// Leggi la velocità in RPM per il vinile
rpmStr, _ := reader.ReadString('\n')
rpmStr = strings.TrimSpace(rpmStr)
rpm, _ := strconv.Atoi(rpmStr)
// Leggi il nome della traccia
track, _ = reader.ReadString('\n')
track = strings.TrimSpace(track)
// TODO: Crea un VinylPlayer con il valore RPM indicato
// TODO: Racchiudilo in un VinylAdapter
// TODO: Assegnalo alla variabile player
_ = rpm // Rimuovi questa riga quando usi rpm
} else {
// Leggi il nome della traccia
track, _ = reader.ReadString('\n')
track = strings.TrimSpace(track)
// TODO: Crea un DigitalPlayer
// TODO: Assegnalo alla variabile player
}
// TODO: Chiama PlayMusic e stampa il risultato
_ = player // Rimuovi questa riga quando usi player
_ = track // Rimuovi questa riga quando usi track
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti di OOP in Go
File esterniWorkspace e moduli GoPacchetti e importazioniNomi esportati e non esportatiIntroduzione all’OOP in GoGli struct come classiDefinire metodi sugli structReceiver con puntatore vs receiver con valoreInizializzazione degli structFunzioni costruttriciRiepilogo - Calcolatrice semplice4Interfacce
Introduzione alle interfacceImplementazione implicitaL’interfaccia come contrattoInterfaccia vuota (any)Asserzione di tipoSwitch sul tipoComposizione di interfacceInterfacce Stringer ed ErrorRipasso - Calcolatore di forme7Incapsulamento
Campi esportati e non esportatiIncapsulamento a livello di packageMetodi Getter e SetterNascondimento delle informazioni in GoRiepilogo - Registri degli studenti10Tipi generici (Go 1.18+)
Introduzione ai tipi genericiParametri di tipoVincoli di tipoStruct genericheSoluzione alternativa per i metodi genericiRiepilogo - Collezione generica13Modelli di progettazione Parte 1
Introduzione ai modelli di progettazioneModello SingletonModello FactoryModello Abstract FactoryModello ObserverModello Strategy2Approfondimento su tipi e struct
Tipi di base e compostiDefinizioni di tipi personalizzatiTag delle structStruct anonimeStruct annidateValori zero e predefinitiRipasso - Rubrica5Composizione invece dell'ereditarietà
Perché Go non ha l'ereditarietàFondamenti dell'embedding delle structPromozione dei metodiEmbedding di più structEmbedding e aggregazione a confrontoMascheramento dei metodi embeddedRiepilogo: gerarchia dei dipendenti8Gestione degli errori e OOP
L'interfaccia errorTipi di errore personalizzatiWrapping degli errori (fmt.Errorf)Errori sentinellaerrors.Is() ed errors.As()Panic, Defer e RecoverRiepilogo - Parser di file11Libreria standard e OOP
io.Reader e io.Writersort.InterfaceInterfaccia fmt.Stringerencoding/json con le structInterfaccia http.HandlerRipasso - modelli REST API14Modelli di progettazione – Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeMiddleware come Decorator3Puntatori e memoria
Concetti base dei puntatori in GoPuntatori a structPassaggio per valore vs per riferimentoLa funzione new()Garbage collection in GoRiepilogo - Costruttore di liste concatenate6Polimorfismo in Go
Polimorfismo tramite interfacceDuck typing in GoRegole di soddisfacimento delle interfacceCollezioni polimorficheIniezione delle dipendenzeRiepilogo - Elaboratore di pagamenti9Concorrenza e OOP
Basi delle goroutineCanali e comunicazioneCanali bufferizzati vs non bufferizzatiIstruzione selectsync.Mutex e sync.RWMutexsync.WaitGroupProgettazione di struct thread-safeRiepilogo: worker poolEsercitati da solo: Compilatore Go online