Pattern Command
Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 91 di 107.
Il pattern Command incapsula una richiesta come oggetto, permettendoti di parametrizzare le operazioni, metterle in coda o supportare la funzionalità di annullamento. Mentre Strategy incapsula algoritmi, Command incapsula azioni complete insieme ai relativi parametri.
In Go, definiamo un'interfaccia Command con un metodo Execute, quindi creiamo comandi concreti che contengono tutte le informazioni necessarie per eseguire un'azione:
type Command interface {
Execute() string
}
type Light struct {
IsOn bool
}
type TurnOnCommand struct {
light *Light
}
func (c *TurnOnCommand) Execute() string {
c.light.IsOn = true
return "Light turned on"
}
type TurnOffCommand struct {
light *Light
}
func (c *TurnOffCommand) Execute() string {
c.light.IsOn = false
return "Light turned off"
}Un invoker memorizza ed esegue i comandi senza sapere cosa fanno:
type RemoteControl struct {
command Command
}
func (r *RemoteControl) SetCommand(c Command) {
r.command = c
}
func (r *RemoteControl) PressButton() string {
return r.command.Execute()
}L'invoker è completamente disaccoppiato dal ricevitore (la Light):
light := &Light{}
remote := &RemoteControl{}
remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton()) // Luce accesa
remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton()) // Luce spentaCommand è ideale per implementare sistemi di annullamento/ripetizione, code di attività o registrazione di macro, quando devi memorizzare, ritardare o ripetere operazioni.
Sfida
FacileCostruiamo un editor di testo con funzionalità di annullamento usando il pattern Command! Creerai comandi che incapsulano le operazioni sul testo, consentendo all’editor di eseguire azioni e annullarle: un classico caso d’uso che dimostra quanto siano potenti i comandi.
Organizzerai il codice in tre file:
command.go: definisci l’interfaccia dei comandi e i comandi concreti per la modifica del testo.Crea un’interfaccia
Commandcon due metodi:Execute() stringper eseguire l’azione eUndo() stringper annullarla.Implementa due tipi di comando che operano su un
*TextEditor:InsertCommand: contiene il puntatore all’editor e il testo da inserire. Execute aggiunge il testo al contenuto dell’editor e restituisceInserted: [text]. Undo rimuove quel testo dalla fine e restituisceUndone insert: [text]DeleteCommand: contiene il puntatore all’editor e il numero di caratteri da eliminare dalla fine. Execute rimuove quei caratteri (salvandoli per l’annullamento) e restituisceDeleted: [removed text]. Undo li ripristina e restituisceUndone delete: [restored text]
editor.go: crea l’editor di testo (il ricevitore) e l’invocatore che gestisce la cronologia dei comandi.Crea una struct
TextEditorcon un campoContent(stringa) e i metodiAppend(text string)eDeleteLast(count int) string(che restituisce il testo eliminato).Crea una struct
EditorInvokerche contiene una slice dei comandi eseguiti come cronologia. Aggiungi questi metodi:ExecuteCommand(cmd Command) string: esegue il comando, lo aggiunge alla cronologia e restituisce il risultatoUndoLast() string: rimuove l’ultimo comando dalla cronologia, ne chiama Undo e restituisce il risultato. Se la cronologia è vuota, restituisceNothing to undo
main.go: mostra il funzionamento del sistema di comandi con una serie di operazioni.Leggi il numero di operazioni. Per ogni operazione, leggi il tipo di operazione (
insert,deleteoundo). Per insert, leggi anche il testo da inserire. Per delete, leggi il numero di caratteri. Esegui ogni operazione tramite l’invocatore e stampa il risultato. Dopo tutte le operazioni, stampa il contenuto finale dell’editor comeFinal: [content].
Verranno forniti i seguenti input:
- Riga 1: numero di operazioni
- Per ogni operazione: tipo di operazione, poi eventuali dati aggiuntivi (testo per insert, numero per delete)
Per esempio, dato:
5
insert
Hello
insert
World
delete
3
undo
undoL’output dovrebbe essere:
Inserted: Hello
Inserted: World
Deleted: rld
Undone delete: rld
Undone insert: World
Final: HelloE dato:
4
insert
Go
insert
Lang
undo
insert
!L’output dovrebbe essere:
Inserted: Go
Inserted: Lang
Undone insert: Lang
Inserted: !
Final: Go!E dato:
2
undo
insert
TestL’output dovrebbe essere:
Nothing to undo
Inserted: Test
Final: TestNota come ogni comando incapsuli tutto ciò che serve sia per eseguire la propria azione sia per annullarla. L’invocatore non sa cosa fanno i comandi. Si limita a eseguirli e a mantenere la cronologia per supportare l’annullamento!
Provalo tu
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leggi il numero di operazioni
line, _ := reader.ReadString('\n')
numOps, _ := strconv.Atoi(strings.TrimSpace(line))
// Crea l'editor e l'invocatore
editor := &TextEditor{}
invoker := &EditorInvoker{}
for i := 0; i < numOps; i++ {
// Leggi il tipo di operazione
opLine, _ := reader.ReadString('\n')
opType := strings.TrimSpace(opLine)
var result string
switch opType {
case "insert":
// Leggi il testo da inserire
textLine, _ := reader.ReadString('\n')
text := strings.TrimSuffix(textLine, "\n")
// TODO: Crea un InsertCommand ed eseguilo tramite l'invocatore
_ = text
_ = editor
case "delete":
// Leggi il numero di caratteri da eliminare
countLine, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countLine))
// TODO: Crea un DeleteCommand ed eseguilo tramite l'invocatore
_ = count
case "undo":
// TODO: Chiama UndoLast sull'invocatore
}
fmt.Println(result)
}
// Stampa il contenuto finale
fmt.Printf("Final: %s\n", editor.Content)
}
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