Skip to content

Commit d9019b4

Browse files
committed
feat(behavioral): add command pattern
1 parent c04463c commit d9019b4

File tree

1 file changed

+134
-0
lines changed

1 file changed

+134
-0
lines changed

behavioral/command.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
interface ICommand {
2+
execute(): void;
3+
undo(): void;
4+
}
5+
6+
class CopyCommand implements ICommand {
7+
constructor(
8+
private readonly editor: Editor,
9+
private snapshot = { clipboard: editor.clipboard }
10+
) {}
11+
12+
execute() {
13+
this.snapshot.clipboard = this.editor.clipboard;
14+
15+
const [startIndex, endIndex] = this.editor.getSelection();
16+
this.editor.clipboard = this.editor.text.substring(startIndex, endIndex);
17+
}
18+
19+
undo() {
20+
// not implemented.
21+
}
22+
}
23+
24+
class PasteCommand implements ICommand {
25+
constructor(
26+
private readonly editor: Editor,
27+
private snapshot = {
28+
clipboard: editor.clipboard,
29+
cursorIndex: editor.cursorIndex,
30+
}
31+
) {}
32+
33+
execute() {
34+
this.snapshot.clipboard = this.editor.clipboard;
35+
this.snapshot.cursorIndex = this.editor.cursorIndex;
36+
37+
this.editor.text =
38+
this.editor.text.slice(0, this.snapshot.cursorIndex) +
39+
this.editor.clipboard +
40+
this.editor.text.slice(this.snapshot.cursorIndex);
41+
}
42+
43+
undo() {
44+
this.editor.text =
45+
this.editor.text.slice(0, this.snapshot.cursorIndex) +
46+
this.editor.text.slice(
47+
this.snapshot.cursorIndex + this.snapshot.clipboard.length
48+
);
49+
}
50+
}
51+
52+
class Editor {
53+
constructor(
54+
public text: string,
55+
public history: ICommand[] = [],
56+
public clipboard: string = ""
57+
) {}
58+
59+
get cursorIndex() {
60+
return Math.floor(Math.random() * this.text.length);
61+
}
62+
63+
getSelection() {
64+
// const text = document.getSelection();
65+
66+
// random slice of text
67+
const firstIndex = Math.floor(Math.random() * this.text.length);
68+
const secondIndex = Math.floor(Math.random() * this.text.length);
69+
70+
const startIndex = Math.min(firstIndex, secondIndex);
71+
const endIndex = Math.max(firstIndex, secondIndex);
72+
73+
return [startIndex, endIndex];
74+
}
75+
76+
executeCommands(...commands: ICommand[]) {
77+
commands.forEach((command) => {
78+
command.execute();
79+
this.history.push(command);
80+
});
81+
82+
return this;
83+
}
84+
85+
undo(count = 1) {
86+
const undoCommands = this.history.splice(
87+
this.history.length - count,
88+
count
89+
);
90+
91+
undoCommands.reverse().forEach((command) => {
92+
command.undo();
93+
});
94+
}
95+
}
96+
97+
/**
98+
* Command pattern pros
99+
*
100+
* * Single responsibility: Command pattern decouples the request from invoker (ex. UI buttons) and receiver (ex. Editor class).
101+
* * Open/Close principle: Introducing new Commands will not cause any change in application.
102+
* * Easy implementation of undo/redo functionality
103+
* * Composition of Commands into bigger Command
104+
*
105+
* Command pattern cons
106+
*
107+
* * new layer of abstraction, which makes application more complex.
108+
*/
109+
110+
const editor = new Editor(`
111+
Lorem ipsum dolor sit amet,
112+
obcaecati nostrum iure.
113+
`);
114+
115+
const copy = new CopyCommand(editor);
116+
const paste = new PasteCommand(editor);
117+
118+
// This commands, in reality will be called from UI ( invoker ).
119+
editor.executeCommands(copy);
120+
121+
console.log(editor.clipboard);
122+
123+
editor.executeCommands(paste);
124+
125+
console.log(editor.text);
126+
console.log(editor.history.length); // will output 2:
127+
128+
editor.undo();
129+
130+
editor.executeCommands(copy, paste, copy, paste);
131+
editor.undo(4);
132+
133+
console.log(editor.text);
134+
console.log(editor.history.length); // will output 1:

0 commit comments

Comments
 (0)