-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathcomposite_command.cpp
More file actions
158 lines (135 loc) · 3.87 KB
/
composite_command.cpp
File metadata and controls
158 lines (135 loc) · 3.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#include <initializer_list>
#include <iostream>
#include <vector>
using namespace std;
struct BankAccount {
int balance{0};
int overdraft_limit{-500};
void deposit(int amount) {
balance += amount;
cout << "deposited " << amount << ", balance is now " << balance << "\n";
}
bool withdraw(int amount) {
if (balance - amount >= overdraft_limit) {
balance -= amount;
cout << "withdrew " << amount << ", balance is now " << balance << "\n";
return true; // Required to know if operation succeeded.
}
return false;
}
friend std::ostream &operator<<(std::ostream &os,
const BankAccount &account) {
os << "balance: " << account.balance;
return os;
}
};
struct Command {
bool succeeded{false};
virtual void call() = 0;
virtual void undo() = 0;
};
struct BankAccountCommand : Command {
BankAccount &account;
enum Action { deposit, withdraw } action;
int amount;
BankAccountCommand(BankAccount &account, Action action, int amount)
: account{account}, action{action}, amount{amount} {}
void call() override {
switch (action) {
case deposit:
account.deposit(amount);
succeeded = true;
break;
case withdraw:
succeeded = account.withdraw(amount);
break;
}
}
void undo() override {
if (!succeeded) {
return;
}
switch (action) {
case deposit:
account.withdraw(amount);
break;
case withdraw:
// Dealing with this case forces us to break the open-closed principle,
// as we need information on whether the operation succeeded.
// This can be done by having access to a boolean value on the account.
account.deposit(amount);
break;
}
}
};
/**
* This CompositeBankAccountCommand is equivalent to a macro (vector of
* commands), which is also a command providing call() and undo() methods.
*/
struct CompositeBankAccountCommand : vector<BankAccountCommand>, Command {
explicit CompositeBankAccountCommand(
const initializer_list<BankAccountCommand> &items)
: vector(items) {}
void call() override {
for (auto &cmd : *this) {
cmd.call();
}
}
void undo() override {
for (auto it = rbegin(); it != rend(); ++it) {
it->undo();
}
}
};
/**
* This intermediate class with this call() implementation is needed to
* maintain consistency between multiple commands when failure is possible.
*/
struct DependentCompositeCommand : CompositeBankAccountCommand {
explicit DependentCompositeCommand(
const initializer_list<BankAccountCommand> &items)
: CompositeBankAccountCommand(items) {}
// This implementation only cares about stopping the pipeline on failure.
// A better implementation can implement rollback semantics, to undo all
// operations.
void call() override {
bool ok = true;
for (auto &cmd : *this) {
// As soon as a command fails:
// - next commands wont be executed.
// - next commands are marked as failed.
if (ok) {
cmd.call();
ok = cmd.succeeded;
} else {
cmd.succeeded = false;
}
}
}
};
/**
* Transfer is modeled as a withdrawal + deposit
*/
struct MoneyTransferCommand : DependentCompositeCommand {
explicit MoneyTransferCommand(BankAccount &from, BankAccount &to, int amount)
: DependentCompositeCommand({
BankAccountCommand{from, BankAccountCommand::withdraw, amount},
BankAccountCommand{to, BankAccountCommand::deposit, amount},
}) {}
};
int main() {
BankAccount ba;
BankAccount ba2;
ba.deposit(100);
auto cmd1 = MoneyTransferCommand(ba, ba2, 25);
cmd1.call();
cmd1.undo();
cout << ba << " - " << ba2 << endl;
cout << endl;
auto cmd2 = MoneyTransferCommand(ba, ba2, 5000);
cmd2.call();
cout << ba << " - " << ba2 << endl;
cmd2.undo();
cout << ba << " - " << ba2 << endl;
return 0;
}