Julio Biason
3 years ago
1 changed files with 118 additions and 0 deletions
@ -0,0 +1,118 @@ |
|||||||
|
+++ |
||||||
|
title = "Command Pattern Experiments in Rust" |
||||||
|
date = 2021-07-22 |
||||||
|
|
||||||
|
[taxonomies] |
||||||
|
tags = ["design patterns", "command", "rust"] |
||||||
|
+++ |
||||||
|
|
||||||
|
I've been playing a bit with the command pattern in Rust and found at least two |
||||||
|
ways to write it. |
||||||
|
|
||||||
|
<!-- more --> |
||||||
|
|
||||||
|
## But first... why? |
||||||
|
|
||||||
|
There is one thing I'm trying to do with all this: I want to have a library |
||||||
|
with all the actions and then plug interfaces on top of, being either a CLI |
||||||
|
interface or a Web interface or whatever interface. For that, the logic behind |
||||||
|
the action should be somewhat isolated from whatever source it is calling it. |
||||||
|
And the command interface fits perfectly in this. |
||||||
|
|
||||||
|
## The Enum Solution |
||||||
|
|
||||||
|
Because you have a list of actions, one of the ideas was to use `Enums`. |
||||||
|
|
||||||
|
Say, we have two actions the user can trigger: deposit money and withdraw |
||||||
|
money. Simple. |
||||||
|
|
||||||
|
So one could have the following Enum: |
||||||
|
|
||||||
|
```rust |
||||||
|
enum Command { |
||||||
|
Deposit(Decimal), |
||||||
|
Withdraw(Decimal), |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Because Rust allows enum variants to carry a value with them, the amount to be |
||||||
|
deposited/withdraw is attached directly to the variant. |
||||||
|
|
||||||
|
And then you have the `execute()` function. And, again, 'cause Rust allows |
||||||
|
adding functions to almost everything, what I did was: |
||||||
|
|
||||||
|
```rust |
||||||
|
impl Command { |
||||||
|
fn execute(&self) -> Result<...> { |
||||||
|
match self { |
||||||
|
Deposit(value) => do_the_deposit(value), |
||||||
|
Withdraw(value) => withdraw_money(value), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
... and so on. |
||||||
|
|
||||||
|
It feels fine and all, but it tends to make a mess with the amount of content |
||||||
|
that goes in or around the `impl`, in my opinion. But, at the same time, means |
||||||
|
that our interface layer must just return `Command` and that's all fine. |
||||||
|
|
||||||
|
One solution to the amount of "content in or around `impl`" could be use |
||||||
|
multiple `impl`: So I could have a module `deposit.rs` which `impl`s the |
||||||
|
`do_the_deposit` and another module `withdraw.rs` which also `impl`s inside the |
||||||
|
enum with the `withdraw_money` content. But I'd still need the center `execute` |
||||||
|
to do the proper "dispatch" of the calls. |
||||||
|
|
||||||
|
## The Trait Solution |
||||||
|
|
||||||
|
The trait solution is very close to what the pattern is: You create a trait and |
||||||
|
"impl" it for all the commands. For example: |
||||||
|
|
||||||
|
```rust |
||||||
|
trait Command { |
||||||
|
fn execute(&self) -> Result<...>; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
```rust |
||||||
|
struct Deposit(Decimal); |
||||||
|
impl Command for Deposit { |
||||||
|
fn execute(&self) -> Result<...> { |
||||||
|
// what was `do_the_deposit` now goes here. |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
struct Withdraw(Decimal); |
||||||
|
impl Command for Withdraw { |
||||||
|
fn execute(&self) -> Result<...> { |
||||||
|
// what was `withdraw_money` now goes here. |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
This causes a slight problem with the interface layer: Now it can't just return |
||||||
|
one sized thing: It needs to return a dynamic dispatchable content, like |
||||||
|
`Box<dyn Command>`, which isn't as pretty as the direct Enum/Struct/sized |
||||||
|
content from the Enum. |
||||||
|
|
||||||
|
On the other hand, since `Box` implements `Deref`, once the interface throws |
||||||
|
something-that-implements-Command, one could just call `execute()` directly on |
||||||
|
it. |
||||||
|
|
||||||
|
```rust |
||||||
|
let command = interface_that_returns_a_box_dyn_command(); |
||||||
|
command.execute(); |
||||||
|
``` |
||||||
|
|
||||||
|
Also, because all commands are structures, they can hold the same information |
||||||
|
as Enum variants would. |
||||||
|
|
||||||
|
## Where I see those two |
||||||
|
|
||||||
|
I can see the Enum being used for simple, single domain architectures. Since |
||||||
|
all things are related, they can reside correctly under the Enum. |
||||||
|
|
||||||
|
But when dealing with multiple domains, the trait/dynamic dispatch feels more |
||||||
|
at home: Related things get into their own module, in their own space and the |
||||||
|
idea of mixing them goes on layer above. |
Loading…
Reference in new issue