Introduzione ai modelli di progettazione
Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 85 di 107.
I pattern di progettazione sono soluzioni riutilizzabili a problemi comuni che si presentano nella progettazione del software. Non sono codice da copiare direttamente, ma piuttosto modelli che guidano il modo in cui strutturi i tuoi tipi e le loro interazioni per risolvere sfide specifiche.
I pattern di progettazione sono generalmente raggruppati in tre categorie:
| Categoria | Scopo | Esempi |
|---|---|---|
| Creazionali | Meccanismi di creazione degli oggetti | Singleton, Factory, Builder |
| Strutturali | Composizione di tipi in strutture più grandi | Adapter, Decorator, Composite |
| Comportamentali | Comunicazione tra oggetti | Observer, Strategy, Command |
In Go, i pattern di progettazione hanno un aspetto diverso rispetto ai linguaggi OOP tradizionali.
Senza classi o ereditarietà, Go implementa i pattern usando interfacce, incorporamento di struct e composizione. Per esempio, dove Java potrebbe usare una classe astratta, Go usa un'interfaccia. Dove C++ potrebbe usare l'ereditarietà, Go usa l'incorporamento.
Imparare i pattern ti aiuta a riconoscere i problemi comuni e a comunicare efficacemente le soluzioni agli altri sviluppatori. Quando qualcuno dice «usa qui il pattern Strategy», tutto il team capisce l’approccio senza bisogno di lunghe spiegazioni.
Nelle prossime lezioni implementerai diversi pattern fondamentali in Go, iniziando con pattern creazionali come Singleton e Factory, per poi passare a pattern comportamentali come Observer e Strategy.
Sfida
FacileCostruiamo un semplice sistema di notifiche che mostri come i pattern di progettazione aiutano a organizzare il codice! Creerai una struttura che in futuro potrà essere facilmente estesa con pattern come Observer o Strategy, ma per ora concentrati sulla creazione di interfacce e tipi ben organizzati, seguendo i principi OOP.
Organizzerai il codice in tre file:
notifier.go: Definisci l'interfaccia principale e i tipi per il tuo sistema di notifiche.Crea un'interfaccia
Notifiercon un solo metodoSend(message string) stringche restituisce un messaggio di conferma.Implementa due tipi concreti di notificatore:
EmailNotifiercon un campoAddress. Il suo metodoSendrestituisceEmail to [address]: [message]SMSNotifiercon un campoPhoneNumber. Il suo metodoSendrestituisceSMS to [phone]: [message]
dispatcher.go: Crea un dispatcher che funzioni con qualsiasi notificatore tramite l'interfaccia.Crea una struct
NotificationDispatcherche contenga una slice di interfacceNotifier. Aggiungi questi metodi:AddNotifier(n Notifier)per registrare un notificatoreBroadcast(message string) []stringche invia il messaggio tramite tutti i notificatori registrati e restituisce una slice di tutte le stringhe di conferma
Crea anche un costruttore
NewNotificationDispatcher()che restituisca un dispatcher inizializzato.main.go: Configura i notificatori e trasmetti i messaggi.Leggi il numero di notificatori da creare. Per ciascun notificatore, leggi il suo tipo (
emailosms) e la sua destinazione (indirizzo o numero di telefono). Aggiungili a un dispatcher.Poi leggi un messaggio da trasmettere. Invialo tramite tutti i notificatori e stampa ogni conferma su una riga separata.
Verranno forniti i seguenti input:
- Riga 1: numero di notificatori
- Per ogni notificatore: tipo (
emailosms), poi la destinazione - Riga finale: messaggio da trasmettere
Per esempio, dato:
3
email
alice@example.com
sms
555-1234
email
bob@example.com
Server maintenance at midnightIl tuo output dovrebbe essere:
Email to alice@example.com: Server maintenance at midnight
SMS to 555-1234: Server maintenance at midnight
Email to bob@example.com: Server maintenance at midnightE dato:
2
sms
555-9999
sms
555-0000
Meeting canceledIl tuo output dovrebbe essere:
SMS to 555-9999: Meeting canceled
SMS to 555-0000: Meeting canceledQuesta struttura mostra come le interfacce consentano di creare progetti flessibili ed estensibili. Il dispatcher non conosce i tipi specifici di notificatore. Sa solo che possono Send. Questa è la base su cui si fondano pattern come Observer e Strategy!
Provalo tu
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leggi il numero di notificatori
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// Crea un nuovo dispatcher
dispatcher := NewNotificationDispatcher()
// Leggi ogni notificatore e aggiungilo al dispatcher
for i := 0; i < count; i++ {
notifierType, _ := reader.ReadString('\n')
notifierType = strings.TrimSpace(notifierType)
destination, _ := reader.ReadString('\n')
destination = strings.TrimSpace(destination)
// TODO: Crea il notificatore appropriato in base al tipo
// e aggiungilo al dispatcher
// Suggerimento: controlla se notifierType è "email" o "sms"
}
// Leggi il messaggio da inviare a tutti
message, _ := reader.ReadString('\n')
message = strings.TrimSpace(message)
// TODO: Invia il messaggio a tutti e stampa ogni conferma
}
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