Commands can have execute and unexecute definitions, thus facilitating undo functionality. let's take a look at a simplified UML representation, the UML i'm using is tailored to my example.
now our invoker maintains a stack of commands that have been fired, this is then used by the undo method to undo whatever it is our command did. Our commands that can be undone are sent to our invoker via the ExecuteCommand method and of course we set our ListPeopleCommand through our constructor; the ListPeopleCommand is an exception, because it can't really be undone.
next let's focus on our ICommand interface it's concrete class and their relationships with the invoker; the ICommand interface simply defines an execute and an unexecute method, one does something, the other undoes that thing. it's really up to us to come up with the logic for both. Our concrete implementation of the ICommand Interface has a reference to a receiver, basically the thing our commands will be modifying. you can notice that it's only the concrete command class that knows anything about the receiver, and this facilitates our decoupling between the invoker and receiver.
First let's take a look at this person class
class Person
{
static int
runningId = 0;
public int Id { get; private set; } = runningId++;
public string Name { get; set; }
public Person(string name) : base() => Name = name;
public string
Display() => $"{Id}) {Name}";
}
interface ICommand
{
void Execute();
void Unexecute();
}
class Invoker {
Stack<ICommand> commandStack = new Stack<ICommand>();
ICommand listPeopleCommand;
public Invoker(ICommand listPeopleCommand) => this.listPeopleCommand =
listPeopleCommand;
public void
ExecuteCommand(ICommand command) {
commandStack.Push(command);
commandStack.Peek().Execute();
}
public void
ListPeople() => listPeopleCommand.Execute();
public void Undo() {
if (commandStack.Count > 0)
commandStack.Pop().Unexecute();
else
Console.WriteLine("Nothing to undo");
}
}
Next let's take a look at the ListPeopleCommand
class ListPeopleCommand : ICommand
{
IList<Person> people;
public ListPeopleCommand(IList<Person> People) => people =
People;
public void
Execute()
{
Console.Clear();
foreach (var person in people)
Console.WriteLine($"\t{person.Display()}");
Console.WriteLine();
}
public void
Unexecute() { //do nothing; }
}
Now let's take a look at the CreatePersonCommand
class CreatePersonCommand : ICommand
{
IList<Person> people;
Person person;
public CreatePersonCommand(IList<Person> People) => people =
People;
public void
Execute()
{
Console.WriteLine("\nEnter in the persons name");
string name = null;
while (String.IsNullOrEmpty(name = Console.ReadLine()));
person = new Person(name);
people.Add(person);
Console.Clear();
Console.WriteLine($"\tCreated:{person.Display()}");
}
public void
Unexecute()
{
people.Remove(person);
Console.WriteLine($"\tUndone:{person.Display()} creation");
}
}
next lets' create UpdatePersonCommand, and DeletePersonCommand.
class UpdatePersonCommand : ICommand
{
IList<Person> people;
string oldName;
Person person;
public UpdatePersonCommand(IList<Person> People) => people =
People;
public void
Execute()
{
Console.WriteLine("\nEnter in the id of the person who's name you want to
update");
int personId = -1;
while (!int.TryParse(Console.ReadKey().KeyChar.ToString(),
out personId));
Console.WriteLine("\nEnter in the new name of the person to update");
string newName = null;
while (String.IsNullOrEmpty(newName = Console.ReadLine()));
person= people.FirstOrDefault(p =>
p.Id == personId);
oldName = person.Name;
Console.Clear();
Console.Write($"{person.Display()} to ");
person.Name = newName;
Console.WriteLine(person.Name);
}
public void
Unexecute()
{
Console.Write($"undone rename {person.Name} ");
person.Name = oldName;
Console.WriteLine($"back to {oldName}");
}
}
class DeletePersonCommand : ICommand
{
IList<Person> people;
Person person;
public DeletePersonCommand(IList<Person> People) => people =
People;
public void
Execute()
{
Console.WriteLine("\nEnter in the id of the person who you want to
delete");
int personId = -1;
while (!int.TryParse(Console.ReadKey().KeyChar.ToString(),
out personId));
person = people.FirstOrDefault(p =>
p.Id == personId);
Console.Clear();
Console.WriteLine($"\tRemoved:{person.Display()}\r\n");
people.Remove(person);
}
public void
Unexecute()
{
people.Add(person);
Console.WriteLine($"undone delete: added person {person.Display()} back");
}
}
class Program
{
static void Main(string[] args)
{
var selection = -1;
var people = new List<Person>(); /* Receiver */
var invoker = new Invoker(new ListPeopleCommand(people));
do
{
switch (selection)
{
case 1:
invoker.ExecuteCommand(new CreatePersonCommand(people));
break;
case 2:
invoker.ExecuteCommand(new UpdatePersonCommand(people));
break;
case 3:
invoker.ExecuteCommand(new DeletePersonCommand(people));
break;
case 4:
invoker.ListPeople();
break;
case 9:
invoker.Undo();
break;
}
ListCommands();
while (!int.TryParse(Console.ReadKey().KeyChar.ToString(),
out selection));
} while (selection > 0);
}
static void
ListCommands()
{
Console.WriteLine("1) Add person");
Console.WriteLine("2) Update person");
Console.WriteLine("3) Delete person");
Console.WriteLine("4) List people");
Console.WriteLine("9) undo");
Console.WriteLine("0) Exit");
}
}
with this approach we can easily execute commands and undo them.
a quick caveat is that the app itself is very brittle there's almost no preventative measures in place so if you try to delete or edit an item that's not there well the app will crash.
here's the full code from start to finish.
using System;
using System.Collections.Generic;
using System.Linq;
namespace pav.commandPattern
{
class Person
{
static int
runningId = 0;
public int Id { get; set; } = runningId++;
public string Name { get; set; }
public Person(string name) : base() => Name = name;
public string
Display() => $"{Id}) {Name}";
}
interface ICommand
{
void Execute();
void Unexecute();
}
class CreatePersonCommand : ICommand
{
IList<Person> people;
Person person;
public CreatePersonCommand(IList<Person> People) => people =
People;
public void
Execute()
{
Console.WriteLine("\nEnter in the persons name");
string name = null;
while (String.IsNullOrEmpty(name = Console.ReadLine()));
person = new Person(name);
people.Add(person);
Console.Clear();
Console.WriteLine($"\tCreated:{person.Display()}");
}
public void
Unexecute()
{
people.Remove(person);
Console.WriteLine($"\tUndone:{person.Display()} creation");
}
}
class UpdatePersonCommand : ICommand
{
//reciever
IList<Person> people;
string oldName;
Person person;
public UpdatePersonCommand(IList<Person> People) => people =
People;
public void
Execute()
{
Console.WriteLine("\nEnter in the id of the person who's name you want to
update");
int personId = -1;
while (!int.TryParse(Console.ReadKey().KeyChar.ToString(),
out personId));
Console.WriteLine("\nEnter in the new name of the person to update");
string newName = null;
while (String.IsNullOrEmpty(newName = Console.ReadLine()));
person = people.FirstOrDefault(p
=> p.Id == personId);
oldName = person.Name;
Console.Clear();
Console.Write($"{person.Display()} to ");
person.Name = newName;
Console.WriteLine(person.Name);
}
public void
Unexecute()
{
Console.Write($"undone rename {person.Name} ");
person.Name = oldName;
Console.WriteLine($"back to {oldName}");
}
}
class DeletePersonCommand : ICommand
{
IList<Person> people;
Person person;
public DeletePersonCommand(IList<Person> People) => people =
People;
public void
Execute()
{
Console.WriteLine("\nEnter in the id of the person who you want to
delete");
int personId = -1;
while (!int.TryParse(Console.ReadKey().KeyChar.ToString(),
out personId));
person = people.FirstOrDefault(p
=> p.Id == personId);
Console.Clear();
Console.WriteLine($"\tRemoved:{person.Display()}\r\n");
people.Remove(person);
}
public void Unexecute()
{
people.Add(person);
Console.WriteLine($"undone delete: added person {person.Display()} back");
}
}
class ListPeopleCommand : ICommand
{
IList<Person> people;
public
ListPeopleCommand(IList<Person> People) => people = People;
public void
Execute()
{
Console.Clear();
Console.WriteLine("\nPeople");
foreach (var person in people)
Console.WriteLine($"\t{person.Display()}");
Console.WriteLine();
}
public void
Unexecute() {/*do nothing*/}
}
class Invoker
{
Stack<ICommand> commandStack = new Stack<ICommand>();
ICommand ListPeopleCommand;
public Invoker(ICommand listPeopleCommand) => ListPeopleCommand =
listPeopleCommand;
public void
ExecuteCommand(ICommand command)
{
commandStack.Push(command);
commandStack.Peek().Execute();
}
public void
ListPeople() => ListPeopleCommand.Execute();
public void Undo()
{
if (commandStack.Count > 0)
commandStack.Pop().Unexecute();
else
Console.WriteLine("Nothing to undo");
}
}
class Program
{
static void Main(string[] args)
{
var selection = -1;
var people = new
List<Person>(); /* Receiver */
var invoker = new Invoker(new ListPeopleCommand(people));
do
{
switch (selection)
{
case 1:
invoker.ExecuteCommand(new CreatePersonCommand(people));
break;
case 2:
invoker.ExecuteCommand(new UpdatePersonCommand(people));
break;
case 3:
invoker.ExecuteCommand(new DeletePersonCommand(people));
break;
case 4:
invoker.ListPeople();
break;
case 9:
invoker.Undo();
break;
}
ListCommands();
while (!int.TryParse(Console.ReadKey().KeyChar.ToString(),
out selection));
} while (selection > 0);
}
static void
ListCommands()
{
Console.WriteLine("1) Add person");
Console.WriteLine("2) Update person");
Console.WriteLine("3) Delete person");
Console.WriteLine("4) List people");
Console.WriteLine("9) undo");
Console.WriteLine("0) Exit");
}
}
}