Skip to content

Commit 6c2f82c

Browse files
committed
grrr
1 parent ee80471 commit 6c2f82c

File tree

5 files changed

+186
-75
lines changed

5 files changed

+186
-75
lines changed

docs/document/Csharp Design Patterns/docs/Behavioural/Command.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ It's heavily used by cli implementations and GUI development.
1313

1414
## ICommand
1515

16+
A simple command implementation is a `ICommand` interface + an object can be applied on by commands.
17+
18+
A Command should be a immutable object which we will represent it as a record.
19+
1620
```cs
1721
var editor = new Editor(content: "hello");
1822
// wrap command info inside a instance
@@ -158,3 +162,86 @@ public class Editor
158162
}
159163
}
160164
```
165+
166+
## Composite Command
167+
168+
A composite command is the combination of Command pattern and Composite pattern.
169+
170+
- Collection like to store commands with order.
171+
- Execute as chaining, be aware to handle exceptions.
172+
- Undo as chaining, be aware to handle exceptions.
173+
174+
> [!tip]
175+
> Commands might also need context to perform actions one by one.
176+
177+
A base class can be like the following.
178+
179+
- A composite command should be `ICommand` too.
180+
- A composite command should be a collection like command.
181+
- Default `Execute` can revoke all executed when any command failed.
182+
183+
```cs
184+
using System.Runtime.InteropServices;
185+
186+
public interface ICommand
187+
{
188+
void Execute();
189+
void Undo();
190+
bool Success { get; set; }
191+
}
192+
193+
public abstract class CompositeCommand<T> : List<T>, ICommand where T : class?, ICommand
194+
{
195+
public bool Success
196+
{
197+
get => this.All(cmd => cmd.Success);
198+
set => field = value;
199+
}
200+
201+
public virtual void Execute()
202+
{
203+
foreach (var cmd in this)
204+
{
205+
cmd.Execute();
206+
// if any cmd failed, revoke all executed // [!code highlight]
207+
if (!cmd.Success) // [!code highlight]
208+
{ // [!code highlight]
209+
var reverse = CollectionsMarshal.AsSpan(this)[..IndexOf(cmd)]; // [!code highlight]
210+
reverse.Reverse(); // [!code highlight]
211+
foreach (var c in reverse) // [!code highlight]
212+
c.Undo(); // [!code highlight]
213+
// [!code highlight]
214+
return; // [!code highlight]
215+
} // [!code highlight]
216+
}
217+
218+
Success = true;
219+
}
220+
221+
public virtual void Undo()
222+
{
223+
foreach (var cmd in Enumerable.Reverse(this))
224+
// only undo executed
225+
if (cmd.Success) cmd.Undo();
226+
}
227+
}
228+
```
229+
230+
```cs
231+
var editor = new Editor(content: "hello");
232+
233+
var commandInsert = new EditorCommand(editor, EditorCommandType.Insert, ", world");
234+
var commandDelete = new EditorCommand(editor, EditorCommandType.Delete, 1);
235+
// deletion exceeds the length. will revoke all executions. // [!code highlight]
236+
var wrongCommand = new EditorCommand(editor, EditorCommandType.Delete, 100); // [!code highlight]
237+
238+
// should edit back to `hello` // [!code highlight]
239+
var combined = new EditorCompositeCommand() { commandInsert, commandDelete, wrongCommand }; // [!code highlight]
240+
241+
combined.Execute();
242+
243+
class EditorCompositeCommand : CompositeCommand<EditorCommand>
244+
{
245+
246+
}
247+
```

0 commit comments

Comments
 (0)