In breve, sì, funzionerà. La risposta più lunga inizia dicendo dipende .
Il modello di comando
Quello che hai descritto è lo schema di comando; questo è un argomento abbastanza ampio ma riassume come segue:
link
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
In termini semplici, ogni azione eseguita dall'utente viene archiviata insieme alle istruzioni su come annullare quell'azione. Proprio come hai descritto nella tua domanda.
Oggetto orientato
Ai fini dell'efficienza, suggerirei un approccio orientato agli oggetti piuttosto che un semplice array di dati. L'array di dati diventerà rapidamente ingestibile all'aumentare del numero di operazioni supportate; un approccio orientato agli oggetti fornirà una base di codice molto più adatta. Il motivo principale è che hai un chiaro singolo punto di responsabilità quando definisci come un'operazione deve essere done e annullata .
Tutte le operazioni sono definite come classi che implementano un'interfaccia comune; per esempio:
interface Operation
{
public function do();
public function undo();
}
Ciascuno degli oggetti "operazione" contiene tutte le proprietà e i metodi richiesti per preparare ed eseguire l'operazione. Si dispone quindi di un oggetto "unità di lavoro" che tiene traccia delle operazioni eseguite dall'utente. Si tratta semplicemente di memorizzare gli oggetti operativi in un contenitore.
Puoi quindi annullare le azioni istruendo la 'unità di lavoro' per annullare l'ultima operazione. La classe 'unità di lavoro' potrebbe essere simile a:
class UnitOfWork
{
private $history;
private $pos;
public function __construct()
{
$this->history = [];
$this->pos = false;
}
public function do(Operation $op)
{
// Try to perform the operation, handle errors as you go
try {
$op->do();
} catch(Exception $e) {
throw new RuntimeException('Unknown error when executing the operation; error: '.$e->getMessage());
}
// If successful, discard any previously undone actions and add the new action to the array
if ($this->pos === false) {
$this->history = [$op];
$this->pos = 0;
} else {
$this->history = array_slice($this->history, 0, ($this->pos+1));
$this->history[] = $op;
$this->pos++;
}
}
public function undo()
{
// Try to undo the last action, handle errors as you go
if ($this->pos != false) {
try {
$this->history[$this->pos]->undo();
} catch(Exception $e) {
throw new RuntimeException('Unknown error when undoing the operation; error: '.$e->getMessage());
}
} else {
throw new UnderflowException('There are no operations to undo');
}
// if successful, move back one place in the history
if ($this->pos <= 0) {
$this->pos = false;
} else {
$this->pos--;
}
}
public function redo()
{
// Try to redo the next action, handle errors as you go
if (count($this->history) > ($this->pos+1) || count($this->history) > 0 && ($this->pos===false)) {
try {
$this->history[$this->pos+1]->do();
} catch(Exception $e) {
throw new RuntimeException('Unknown error when redoing the operation; error: '.$e->getMessage());
}
} else {
throw new UnderflowException('There are no operations to redo');
}
// if successful, move forward one place in the history
if ($this->pos === false) {
$this->pos = 0;
} else {
$this->pos++;
}
}
}
Se desideri un esempio funzionante del modello di comando , puoi trovarne uno qui: link
Le complicazioni
Alcune operazioni non hanno un'inversione chiara. Questo di solito è dovuto al fatto che le informazioni richieste per annullare l'azione originale vengono distrutte durante l'esecuzione dell'atto. L'unico modo per aggirare questo è quello di registrare in modo esplicito le informazioni che dovranno successivamente annullare l'azione.
@amon ha già fornito una descrizione di questo nella sua risposta, quindi leggi la sua risposta per maggiori dettagli sulle potenziali complicazioni.