Solo per estendere un po 'la risposta già eccellente di Robert Harvey in un modo che potrebbe fare un po' più clic con te:
Isn't this functionality achievable with a simple interface? In all the other, more general books the main benefits of this pattern are: postponing the execution, queuing commands, logging and undo functionality.
In questo caso stai utilizzando direttamente il ricevitore invece di avere questa astrazione da comando nel mezzo.
Let's say I crate an ICommandable interface instead of using the commands. How is that different functionally? I know that it's different structurally: I'm completely omitting the [Command object] part, but I can't see what's the decoupling advantage here. In both cases I need to know what's the receiver, when creating the command/calling the interface function. Am I missing something?
Innanzitutto, se vuoi introdurre nuovi comandi sul tuo sistema, allora richiederebbe modifiche centralizzate piuttosto intrusive all'interfaccia ICommandable
. Inoltre ogni nuova funzione aggiunta a quell'interfaccia dovrebbe essere implementata da ogni concreto Commandable
che implementa quell'interfaccia che potrebbe rendere tali estensioni particolarmente costose se ne hai più di una.
In secondo luogo, non ti permette di inserire nuove funzionalità tra invoker e ricevitore. Annullamento e registrazione, accodamento e parallelizzazione sono solo alcuni esempi del tipo di funzionalità che potresti voler aggiungere (e anche possibilmente con il senno di poi) tra invoker e ricevitore.
Nel mio caso, oltre a tutto ciò, abbiamo anche i comandi che vengono registrati da terze parti i cui plug-in sono caricati dagli utenti in fase di runtime (usiamo il modello factory per consentire tale registrazione e l'istanziazione dei comandi per nome). La registrazione di un nuovo comando fa sì che tale comando venga visualizzato automaticamente nell'interfaccia utente e ora sia accessibile sia al codice nativo che al nostro linguaggio di scripting incorporato. Consente a tale comando di essere richiamato in vari modi dagli utenti, facendo clic sugli elementi della GUI o digitando i comandi nella console "command" (scripting) o scrivendo uno script nel proprio file e aggiungendolo come plugin o scrivendo un plugin C ++ e costruendolo e caricandolo dinamicamente. Probabilmente non hai bisogno di tutti questi campanelli e fischietti, ma sono ulteriori esempi di quali tipi di comportamenti ricchi si possono ottenere con lo schema di comando quando lo si combina con factory.
Ma anche tornando alla funzionalità che puoi inserire nel fratempo, nel nostro caso poiché i plug-in scritti da terze parti (che potrebbero non scrivere sempre il codice più sonoro) possono registrare i comandi per l'esecuzione, alcuni plugin mal scritti potrebbero avere una tendenza per arrestare il crash portando con sé la nostra applicazione host in fiamme. Quindi in realtà avviamo un processo separato ed eseguiamo il comando lì con IPC per sincronizzare le modifiche se tale comando ha esito positivo. Ciò ha avuto l'effetto pratico di rendere la nostra applicazione host quasi "a prova di crash".
E questo è solo un altro esempio di funzionalità che puoi inserire tra invocatore e ricevente. È molto utile per la mia esperienza e fornisce un sacco di respiro in questo modo per quello che considero un costo iniziale piuttosto banale (anche se potrei considerarlo banale in parte perché ho usato questo modello di base in una forma o nell'altra per fin da quando posso ricordare)