X Tutup
Skip to content

Commit 4ff196c

Browse files
committed
Refactor the command pattern to use lambda functions
We can leverage the lambda expressins of Java 8 onwards to implement command design pattern instead of traditional non functional way
1 parent 1f4a412 commit 4ff196c

File tree

11 files changed

+206
-434
lines changed

11 files changed

+206
-434
lines changed

command/README.md

Lines changed: 76 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ Action, Transaction
1414

1515
## Intent
1616

17-
Encapsulate a request as an object, thereby letting you parameterize clients with different
17+
Encapsulate a request as an object, thereby letting you parameterize clients with different
1818
requests, queue or log requests, and support undoable operations.
1919

2020
## Explanation
2121
Real world example
2222

23-
> There is a wizard casting spells on a goblin. The spells are executed on the goblin one by one.
24-
> The first spell shrinks the goblin and the second makes him invisible. Then the wizard reverses
23+
> There is a wizard casting spells on a goblin. The spells are executed on the goblin one by one.
24+
> The first spell shrinks the goblin and the second makes him invisible. Then the wizard reverses
2525
> the spells one by one. Each spell here is a command object that can be undone.
2626
2727
In plain words
@@ -30,8 +30,8 @@ In plain words
3030
3131
Wikipedia says
3232

33-
> In object-oriented programming, the command pattern is a behavioral design pattern in which an
34-
> object is used to encapsulate all information needed to perform an action or trigger an event at
33+
> In object-oriented programming, the command pattern is a behavioral design pattern in which an
34+
> object is used to encapsulate all information needed to perform an action or trigger an event at
3535
> a later time.
3636
3737
**Programmatic Example**
@@ -48,27 +48,24 @@ public class Wizard {
4848

4949
public Wizard() {}
5050

51-
public void castSpell(Command command, Target target) {
52-
LOGGER.info("{} casts {} at {}", this, command, target);
53-
command.execute(target);
54-
undoStack.offerLast(command);
51+
public void castSpell(Runnable runnable) {
52+
runnable.run();
53+
undoStack.offerLast(runnable);
5554
}
5655

5756
public void undoLastSpell() {
5857
if (!undoStack.isEmpty()) {
5958
var previousSpell = undoStack.pollLast();
6059
redoStack.offerLast(previousSpell);
61-
LOGGER.info("{} undoes {}", this, previousSpell);
62-
previousSpell.undo();
60+
previousSpell.run();
6361
}
6462
}
6563

6664
public void redoLastSpell() {
6765
if (!redoStack.isEmpty()) {
6866
var previousSpell = redoStack.pollLast();
6967
undoStack.offerLast(previousSpell);
70-
LOGGER.info("{} redoes {}", this, previousSpell);
71-
previousSpell.redo();
68+
previousSpell.run();
7269
}
7370
}
7471

@@ -79,84 +76,7 @@ public class Wizard {
7976
}
8077
```
8178

82-
Next we present the spell hierarchy.
83-
84-
```java
85-
public interface Command {
86-
87-
void execute(Target target);
88-
89-
void undo();
90-
91-
void redo();
92-
93-
String toString();
94-
}
95-
96-
public class InvisibilitySpell implements Command {
97-
98-
private Target target;
99-
100-
@Override
101-
public void execute(Target target) {
102-
target.setVisibility(Visibility.INVISIBLE);
103-
this.target = target;
104-
}
105-
106-
@Override
107-
public void undo() {
108-
if (target != null) {
109-
target.setVisibility(Visibility.VISIBLE);
110-
}
111-
}
112-
113-
@Override
114-
public void redo() {
115-
if (target != null) {
116-
target.setVisibility(Visibility.INVISIBLE);
117-
}
118-
}
119-
120-
@Override
121-
public String toString() {
122-
return "Invisibility spell";
123-
}
124-
}
125-
126-
public class ShrinkSpell implements Command {
127-
128-
private Size oldSize;
129-
private Target target;
130-
131-
@Override
132-
public void execute(Target target) {
133-
oldSize = target.getSize();
134-
target.setSize(Size.SMALL);
135-
this.target = target;
136-
}
137-
138-
@Override
139-
public void undo() {
140-
if (oldSize != null && target != null) {
141-
var temp = target.getSize();
142-
target.setSize(oldSize);
143-
oldSize = temp;
144-
}
145-
}
146-
147-
@Override
148-
public void redo() {
149-
undo();
150-
}
151-
152-
@Override
153-
public String toString() {
154-
return "Shrink spell";
155-
}
156-
}
157-
```
158-
159-
Finally, we have the goblin who's the target of the spells.
79+
Next, we have the goblin who's the target of the spells.
16080

16181
```java
16282
public abstract class Target {
@@ -203,33 +123,73 @@ public class Goblin extends Target {
203123
return "Goblin";
204124
}
205125

126+
public void changeSize() {
127+
var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL;
128+
setSize(oldSize);
129+
}
130+
131+
public void changeVisibility() {
132+
var visible = getVisibility() == Visibility.INVISIBLE
133+
? Visibility.VISIBLE : Visibility.INVISIBLE;
134+
setVisibility(visible);
135+
}
206136
}
207137
```
208138

139+
Finally we have the wizard in main function who casts spell
140+
141+
```java
142+
public static void main(String[] args) {
143+
var wizard = new Wizard();
144+
var goblin = new Goblin();
145+
146+
// casts shrink/unshrink spell
147+
wizard.castSpell(goblin::changeSize);
148+
149+
// casts visible/invisible spell
150+
wizard.castSpell(goblin::changeVisibility);
151+
152+
// undo and redo casts
153+
wizard.undoLastSpell();
154+
wizard.redoLastSpell();
155+
```
156+
209157
Here's the whole example in action.
210158
211159
```java
212160
var wizard = new Wizard();
213161
var goblin = new Goblin();
162+
163+
goblin.printStatus();
164+
wizard.castSpell(goblin::changeSize);
214165
goblin.printStatus();
215-
wizard.castSpell(new ShrinkSpell(), goblin);
166+
167+
wizard.castSpell(goblin::changeVisibility);
216168
goblin.printStatus();
217-
wizard.castSpell(new InvisibilitySpell(), goblin);
169+
170+
wizard.undoLastSpell();
218171
goblin.printStatus();
172+
219173
wizard.undoLastSpell();
220174
goblin.printStatus();
175+
176+
wizard.redoLastSpell();
177+
goblin.printStatus();
178+
179+
wizard.redoLastSpell();
180+
goblin.printStatus();
221181
```
222182
223183
Here's the program output:
224184

225185
```java
226-
// Goblin, [size=normal] [visibility=visible]
227-
// Wizard casts Shrink spell at Goblin
228-
// Goblin, [size=small] [visibility=visible]
229-
// Wizard casts Invisibility spell at Goblin
230-
// Goblin, [size=small] [visibility=invisible]
231-
// Wizard undoes Invisibility spell
232-
// Goblin, [size=small] [visibility=visible]
186+
Goblin, [size=normal] [visibility=visible]
187+
Goblin, [size=small] [visibility=visible]
188+
Goblin, [size=small] [visibility=invisible]
189+
Goblin, [size=small] [visibility=visible]
190+
Goblin, [size=normal] [visibility=visible]
191+
Goblin, [size=small] [visibility=visible]
192+
Goblin, [size=small] [visibility=invisible]
233193
```
234194

235195
## Class diagram
@@ -240,26 +200,26 @@ Here's the program output:
240200

241201
Use the Command pattern when you want to:
242202

243-
* Parameterize objects by an action to perform. You can express such parameterization in a
244-
procedural language with a callback function, that is, a function that's registered somewhere to be
203+
* Parameterize objects by an action to perform. You can express such parameterization in a
204+
procedural language with a callback function, that is, a function that's registered somewhere to be
245205
called at a later point. Commands are an object-oriented replacement for callbacks.
246-
* Specify, queue, and execute requests at different times. A Command object can have a lifetime
247-
independent of the original request. If the receiver of a request can be represented in an address
248-
space-independent way, then you can transfer a command object for the request to a different process
206+
* Specify, queue, and execute requests at different times. A Command object can have a lifetime
207+
independent of the original request. If the receiver of a request can be represented in an address
208+
space-independent way, then you can transfer a command object for the request to a different process
249209
and fulfill the request there.
250-
* Support undo. The Command's execute operation can store state for reversing its effects in the
251-
command itself. The Command interface must have an added un-execute operation that reverses the
252-
effects of a previous call to execute. The executed commands are stored in a history list.
253-
Unlimited-level undo and redo is achieved by traversing this list backwards and forwards calling
210+
* Support undo. The Command's execute operation can store state for reversing its effects in the
211+
command itself. The Command interface must have an added un-execute operation that reverses the
212+
effects of a previous call to execute. The executed commands are stored in a history list.
213+
Unlimited-level undo and redo is achieved by traversing this list backwards and forwards calling
254214
un-execute and execute, respectively.
255-
* Support logging changes so that they can be reapplied in case of a system crash. By augmenting the
256-
Command interface with load and store operations, you can keep a persistent log of changes.
257-
Recovering from a crash involves reloading logged commands from disk and re-executing them with
215+
* Support logging changes so that they can be reapplied in case of a system crash. By augmenting the
216+
Command interface with load and store operations, you can keep a persistent log of changes.
217+
Recovering from a crash involves reloading logged commands from disk and re-executing them with
258218
the execute operation.
259-
* Structure a system around high-level operations build on primitive operations. Such a structure is
260-
common in information systems that support transactions. A transaction encapsulates a set of changes
261-
to data. The Command pattern offers a way to model transactions. Commands have a common interface,
262-
letting you invoke all transactions the same way. The pattern also makes it easy to extend the
219+
* Structure a system around high-level operations build on primitive operations. Such a structure is
220+
common in information systems that support transactions. A transaction encapsulates a set of changes
221+
to data. The Command pattern offers a way to model transactions. Commands have a common interface,
222+
letting you invoke all transactions the same way. The pattern also makes it easy to extend the
263223
system with new transactions.
264224

265225
## Typical Use Case

command/etc/command.png

-25.4 KB
Loading

0 commit comments

Comments
 (0)
X Tutup