Command Examples:
To showcase the Command pattern, we'll be creating a basic implementation of a text editor. This includes the ability to add content(type it on the page), backspace to remove content, and the ability to undo and redo our actions.
We'll start out with a Document class that will contain the text data and actions to add and remove content:
using System.Collections.Generic;
using System;
public class Document
{
string content;
public Document()
{
content = "";
}
public void PrintDocument()
{
Console.WriteLine(content);
}
public void AddContent(string content)
{
this.content += content;
}
public int Length()
{
return content.Length;
}
public string CharAt(int index)
{
return content[index].ToString();
}
public void RemoveContent(int numChars)
{
if(numChars < 0) return;
if(numChars > content.Length - 1)
{
content = "";
return;
}
content = content.Remove(content.Length - numChars);
}
}
Next, we'll want to create an interface for our commands. We'll want to have the usual execute
method, in addition to undo
and redo
methods:
public interface IUndoableCommand
{
void Execute();
void Undo();
void Redo();
}
public class AddContentCommand: IUndoableCommand
{
Document document;
string content;
public AddContentCommand(Document document, string content)
{
this.document = document;
this.content = content;
}
public void Execute()
{
this.document.AddContent(content);
}
public void Undo()
{
this.document.RemoveContent(content.Length);
}
public void Redo()
{
this.Execute();
}
}
public class BackspaceCommand: IUndoableCommand
{
Document document;
String removedChar;
public BackspaceCommand(Document document)
{
this.document = document;
removedChar = null;
}
public void Execute()
{
removedChar = document.CharAt(document.Length() - 1);
this.document.RemoveContent(1);
}
public void Undo()
{
this.document.AddContent(this.removedChar);
}
public void Redo()
{
this.Execute();
}
}
Lastly, we'll want to create a Document writer that will hold onto a list of our actions in order to undo and redo inputs:
public class DocumentWriter
{
Stack<IUndoableCommand> undoStack;
Stack<IUndoableCommand> redoStack;
Document document;
public DocumentWriter(Document document)
{
this.document = document;
this.undoStack = new Stack<IUndoableCommand>();
this.redoStack = new Stack<IUndoableCommand>();
}
public void Write(string content)
{
IUndoableCommand command = new AddContentCommand(this.document, content);
this.undoStack.Push(command);
this.redoStack.Clear();
command.Execute();
}
public void Backspace()
{
if(this.document.Length() == 0) return;
IUndoableCommand command = new BackspaceCommand(this.document);
this.undoStack.Push(command);
this.redoStack.Clear();
command.Execute();
}
public void Undo()
{
if(this.undoStack.Count > 0)
{
IUndoableCommand command = this.undoStack.Pop();
this.redoStack.Push(command);
command.Undo();
}
}
public void Redo()
{
if(this.redoStack.Count > 0)
{
IUndoableCommand command = this.redoStack.Pop();
this.undoStack.Push(command);
command.Redo();
}
}
}
public class Solution
{
public static void Main(string[] args)
{
Document doc = new Document();
DocumentWriter writer = new DocumentWriter(doc);
// Write to the document:
writer.Write("Hello world!");
writer.Backspace();
doc.PrintDocument(); // Hello world
writer.Undo();
doc.PrintDocument(); // Hello world!
writer.Redo();
doc.PrintDocument(); // Hello world
writer.Write(" Goodbye!");
doc.PrintDocument(); // Hello world Goodbye!
writer.Undo();
doc.PrintDocument(); // Hello world
writer.Undo();
doc.PrintDocument(); // Hello world!
}
}
Find any bugs in the code? let us know!