You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
183 lines
11 KiB
183 lines
11 KiB
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
|
<meta http-equiv="content-type" content="text/html; charset=utf-8"> |
|
|
|
<!-- Enable responsiveness on mobile devices--> |
|
<!-- viewport-fit=cover is to support iPhone X rounded corners and notch in landscape--> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, viewport-fit=cover"> |
|
|
|
<title>Julio Biason .Me 4.3</title> |
|
|
|
<!-- CSS --> |
|
<link rel="stylesheet" href="https://blog.juliobiason.me/print.css" media="print"> |
|
<link rel="stylesheet" href="https://blog.juliobiason.me/poole.css"> |
|
<link rel="stylesheet" href="https://blog.juliobiason.me/hyde.css"> |
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=PT+Sans:400,400italic,700|Abril+Fatface"> |
|
|
|
|
|
|
|
|
|
|
|
</head> |
|
|
|
<body class=" "> |
|
|
|
<div class="sidebar"> |
|
<div class="container sidebar-sticky"> |
|
<div class="sidebar-about"> |
|
|
|
<a href="https://blog.juliobiason.me"><h1>Julio Biason .Me 4.3</h1></a> |
|
|
|
<p class="lead">Old school dev living in a 2.0 dev world</p> |
|
|
|
|
|
</div> |
|
|
|
<ul class="sidebar-nav"> |
|
|
|
|
|
<li class="sidebar-nav-item"><a href="/">English</a></li> |
|
|
|
<li class="sidebar-nav-item"><a href="/pt">Português</a></li> |
|
|
|
<li class="sidebar-nav-item"><a href="/tags">Tags (EN)</a></li> |
|
|
|
<li class="sidebar-nav-item"><a href="/pt/tags">Tags (PT)</a></li> |
|
|
|
|
|
</ul> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="content container"> |
|
|
|
<div class="post"> |
|
<h1 class="post-title">Experimentos com Command Pattern em Rust</h1> |
|
<span class="post-date"> |
|
2021-07-22 |
|
|
|
<a href="https://blog.juliobiason.me/pt/tags/design-patterns/">#design patterns</a> |
|
|
|
<a href="https://blog.juliobiason.me/pt/tags/command/">#command</a> |
|
|
|
<a href="https://blog.juliobiason.me/pt/tags/rust/">#rust</a> |
|
|
|
</span> |
|
<p>Eu tenho feito alguns experimentos implementando o command pattern em Rust e |
|
encontrei pelo menos duas formas de implementar.</p> |
|
<span id="continue-reading"></span><h2 id="mas-primeiro-por-que">Mas Primeiro... Por que?</h2> |
|
<p>Existe uma coisa que eu estou tentando fazer em que o command pattern se |
|
encaixa perfeitamente: Eu quero ter uma biblioteca com todas as ações do |
|
sistema e implementar uma interface em cima disso, sendo que pode ser uma CLI |
|
ou uma interface web ou uma interface qualquer. Para isso, a lógica por trás da |
|
ação deve estar de alguma forma isolada da origem da chamada.</p> |
|
<h2 id="o-que-e">O Que É</h2> |
|
<p>O command pattern é descrito como ter um objeto para cada ação (porque, |
|
basicamente, os patterns são mais focados em projetos orientados a objetos) e |
|
cada um destes tem um método chamado <code>execute</code> que... bem... executa o comando.</p> |
|
<h2 id="a-solucao-enum">A Solução Enum</h2> |
|
<p>Como o que você têm é uma lista de ações, uma das ideias foi usar <code>Enum</code>, mesmo |
|
que isso não seja exatamente o que pattern descreve.</p> |
|
<p>Digamos que nós temos duas ações que podem ser chamadas: Depositar dinheiro e |
|
sacar dinheiro. Simples.</p> |
|
<p>Assim, podemos ter o seguinte Enum<sup class="footnote-reference"><a href="#1">1</a></sup>:</p> |
|
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">enum </span><span>Command { |
|
</span><span> Depositar(Decimal), |
|
</span><span> Sacar(Decimal), |
|
</span><span>} |
|
</span></code></pre> |
|
<p>Como Rust permite que as variantes de um Enum carreguem um valor com elas, o |
|
valor a ser depositado ou sacado fica anexado junto com a variante.</p> |
|
<p>E então você tem a função <code>execute()</code>. E, de novo, porque Rust permite que |
|
sejam adicionadas funções em basicamente tudo, o que eu fiz foi adicionar um |
|
método diretamente no Enum:</p> |
|
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">impl </span><span>Command { |
|
</span><span> </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">execute</span><span>(&</span><span style="color:#bf616a;">self</span><span>) -> Result<</span><span style="background-color:#bf616a;color:#2b303b;">.</span><span>..> { |
|
</span><span> </span><span style="color:#b48ead;">match </span><span style="color:#bf616a;">self </span><span>{ |
|
</span><span> Depositar(valor) => </span><span style="color:#96b5b4;">faz_o_deposito</span><span>(valor), |
|
</span><span> Sacar(valor) => </span><span style="color:#96b5b4;">sacar_dinheiro</span><span>(valor), |
|
</span><span> } |
|
</span><span> } |
|
</span><span>} |
|
</span></code></pre> |
|
<p>E assim por diante.</p> |
|
<p>Para usar, eu coloquei algo parecido com isso na minha camada de interface:</p> |
|
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">let</span><span> valor = requisicao_externa.</span><span style="color:#96b5b4;">valor</span><span>(); |
|
</span><span style="color:#b48ead;">let</span><span> comando = </span><span style="color:#b48ead;">match</span><span> requisicao_externa.</span><span style="color:#96b5b4;">comando</span><span>() { |
|
</span><span> "</span><span style="color:#a3be8c;">depositar</span><span>" => Command::Depositar(valor), |
|
</span><span> "</span><span style="color:#a3be8c;">sacar</span><span>" => Command::Sacar(valor), |
|
</span><span>} |
|
</span><span>comando.</span><span style="color:#96b5b4;">execute</span><span>(); |
|
</span></code></pre> |
|
<p>Tudo fica simples e tal, mas existe uma tendência a deixar uma bagunça com a |
|
quantidade de conteúdo que fica dentro ou ao redor do <code>impl</code>, na minha opinião. |
|
Mas, ao mesmo tempo, a camada de dispatch (que fica entre a camada de |
|
serviço/enum e a camada de interface) é bem básica.</p> |
|
<p>Uma solução para para a quantidade de "conteúdo dentro ou ao redor do <code>impl</code>" |
|
seria o uso de múltiplos <code>impl</code>: Ter um módulo <code>deposito.rs</code> que faz o <code>impl</code> |
|
de <code>faz_o_deposito</code> e outro módulo <code>saque.rs</code> que também faz o <code>impl</code> dentro do |
|
enum com o conteúdo de <code>sacar_dinheiro</code>. Mas eu ainda precisaria centrar todas |
|
as operações no <code>execute</code> para ter um dispatch correto.</p> |
|
<h2 id="a-solucao-com-traits">A Solução com Traits</h2> |
|
<p>A solução com trait é bem parecida com o que o pattern diz: Você cria uma trait |
|
(interface) e "impl" em todos os comandos, que são structs. Por exemplo:</p> |
|
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">trait </span><span>Command { |
|
</span><span> </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">execute</span><span>(&</span><span style="color:#bf616a;">self</span><span>) -> Result<</span><span style="background-color:#bf616a;color:#2b303b;">.</span><span>..>; |
|
</span><span>} |
|
</span></code></pre> |
|
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">struct </span><span>Depositar(Decimal); |
|
</span><span style="color:#b48ead;">impl </span><span>Command </span><span style="color:#b48ead;">for </span><span>Depositar { |
|
</span><span> </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">execute</span><span>(&</span><span style="color:#bf616a;">self</span><span>) -> Result <</span><span style="background-color:#bf616a;color:#2b303b;">.</span><span>..> { |
|
</span><span> </span><span style="color:#65737e;">// o que era o `faz_o_deposito` vai aqui. |
|
</span><span> } |
|
</span><span>} |
|
</span><span> |
|
</span><span style="color:#b48ead;">struct </span><span>Sacar(Decimal); |
|
</span><span style="color:#b48ead;">impl </span><span>Command </span><span style="color:#b48ead;">for </span><span>Sacar { |
|
</span><span> </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">execute</span><span>(&</span><span style="color:#bf616a;">self</span><span>) -> Result <</span><span style="background-color:#bf616a;color:#2b303b;">.</span><span>..> { |
|
</span><span> </span><span style="color:#65737e;">// o que era o `sacar_dinheiro` vai aqui. |
|
</span><span> } |
|
</span><span>} |
|
</span></code></pre> |
|
<p>... o que parece um pouco mais limpo, já que todas as coisas relacionadas com |
|
Deposito ou Saque estão juntas agora.</p> |
|
<p>Entretanto, isso causa um pequeno problema com a camada de interface: Agora ela |
|
não pode mais retorna algo com tamanho fixo: É necessário usar um conteúdo com |
|
dispatch dinâmico, como <code>Box<dyn Command></code>, o que não é tão direto quando um |
|
Enum/Struct/conteúdo com tamanho.</p> |
|
<p>Por outro lado, como <code>Box</code> implementa <code>Deref</code>, uma vez que a interface retorne |
|
algo-que-implementa-Command, basta chamada <code>execute()</code> diretamente nele.</p> |
|
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">let</span><span> comando = </span><span style="color:#96b5b4;">interface_que_retorna_um_comando_num_box_dyn</span><span>(); |
|
</span><span>comando.</span><span style="color:#96b5b4;">execute</span><span>(); |
|
</span></code></pre> |
|
<h2 id="onde-eu-vejo-esses-dois">Onde Eu Vejo Esses Dois</h2> |
|
<p>Eu consigo ver o uso do Enum em arquiteturas simples, com apenas um domínio. |
|
Como toas as coisas são relacionadas, elas podem viver tranquilamente dentro do |
|
Enum.</p> |
|
<p>Mas quando estamos lidando com múltiplos domínios, a solução de trait/dispatch |
|
dinâmico parece fazer mais sentido: Coisas relacionadas nos seus próprios |
|
módulos, nos seus próprios espaços e a ideia de misturar os mesmos (por |
|
exemplo, se você tiver um domínio de tags e um domínio de dinheiro, e quer |
|
colocar tags nas operações de dinheiro) ficaria na camada acima deles.</p> |
|
<hr /> |
|
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup> |
|
<p><code>Decimal</code> não faz parte da biblioteca padrão do Rust, mas pode ser usada |
|
a partir da <a href="https://crates.io/crates/rust_decimal">crate rust_decimal</a>.</p> |
|
</div> |
|
<!-- |
|
vim:spelllang=pt: |
|
--> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
</div> |
|
|
|
</body> |
|
|
|
</html>
|
|
|