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, consentendoti 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 invocatore 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'invocatore è completamente disaccoppiato dal destinatario (la luce):
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/ripristino, code di attività o registrazione di macro quando hai bisogno di memorizzare, ritardare o riprodurre operazioni.
Sfida
FacileCostruiamo un editor di testo con funzionalità di annullamento usando il pattern Command! Creerai comandi che incapsulano operazioni sul testo, consentendo all'editor di eseguire azioni e annullarle: un classico caso d'uso che dimostra perché i comandi sono così potenti.
Organizzerai il codice in tre file:
command.go: definisci la tua interfaccia dei comandi e i comandi concreti per modificare il 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 (memorizzandoli per l'annullamento) e restituisceDeleted: [removed text]. Undo li ripristina e restituisceUndone delete: [restored text]
editor.go: crea il tuo editor di testo (il receiver) e l'invoker che gestisce la cronologia dei comandi.Crea una struct
TextEditorcon un campoContent(string) e i metodiAppend(text string)eDeleteLast(count int) string(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, chiama il suo Undo e restituisce il risultato. Se la cronologia è vuota, restituisciNothing to undo
main.go: mostra il tuo sistema di comandi con una serie di operazioni.Leggi il numero di operazioni. Per ciascuna operazione, leggi il tipo (
insert,deleteoundo). Per insert, leggi anche il testo da inserire. Per delete, leggi il numero di caratteri. Esegui ogni operazione tramite il tuo invoker 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)
Ad esempio, dato:
5
insert
Hello
insert
World
delete
3
undo
undoIl tuo 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
!Il tuo output dovrebbe essere:
Inserted: Go
Inserted: Lang
Undone insert: Lang
Inserted: !
Final: Go!E dato:
2
undo
insert
TestIl tuo output dovrebbe essere:
Nothing to undo
Inserted: Test
Final: TestNota come ogni comando incapsula tutto ciò che serve sia per eseguire sia per annullare la propria azione. L'invoker non sa cosa fanno i comandi. Li esegue soltanto e mantiene 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