Implementação Funcional de Design Patterns – Command

Ano passado, Mario Fusco escreveu uma série de posts questionando a forma como programadores Java estão implementando os padrões de projeto definidos pelo GoF. Ele compartilhou uma abordagem funcional, mais simples de ler e de manter .

Eu vou fazer o meu melhor para criar uma versão C# das recomendações de Fusco expandindo alguns exemplos. Vamos começar com o padrão Command.

O padrão Command – como é frequentemente implementado

O padrão Command é um padrão de comportamento em que um objeto é utilizado para encapsular toda informação necessária para executar uma ação ou disparar um evento em um momento futuro.

Em Java, ele geralmente começa a ser implementado pela definição de uma interface:

interface Command {
    void run();
}

Com a interface definida, o programador pode prover diferentes implementações.

public class Logger implements Command {
    public final String message;
 
    public Logger( String message ) {
        this.message = message;
    }
 
    @Override
    public void run() {
        // ..
    }
}

O código que irá consumir o comando não precisa saber detalhes da execução do comando. Algumas implementações vão afetar apenas o estado da aplicação, outras podem salvar dados no disco, por exemplo.

public class FileSaver implements Command {
    public final String message;
 
    public FileSaver( String message ) {
        this.message = message;
    }
 
    @Override
    public void run() {
        // ..
    }
}

Em alguns casos, comandos podem ser utilizados para coordenar trabalho com outros componentes/aplicações.

public class Mailer implements Command {
    public final String message;
 
    public Mailer( String message ) {
        this.message = message;
    }
 
    @Override
    public void run() {
        // ..
    }
}

É uma boa prática ter algo que coordene a execução de uma ou mais instâncias.

public class Executor {
    public void execute(List<Command> tasks) {
        for (Command task : tasks) {
            task.run();
        }
    }
}

Assim, é possível criar uma lista de comandos que se deseja executar.

List<Command> tasks = new ArrayList<>();
tasks.add(new Logger( "Hi" ));
tasks.add(new FileSaver( "Cheers" ));
tasks.add(new Mailer( "Bye" ));
 
new Executor().execute( tasks );

Comandos, implementados desata forma, são apenas funções envolvidas em objetos.

O padrão Command – conforme recomendação de Fusco

O conceito de FunctionalInterface permite a implementação desse padrão de forma muito mais expressiva.

@FunctionalInterface
interface Command {
    void run();
}

Se você não é familiarizado com a anotação @FunctionalInterface, ela pode ser usada com interfaces que definem um único método abstrato – como a interface Command em nosso exemplo. Interfaces funcionais podem ser representadas com uma expressão lambda simples ou com uma referência para um método.

Java já oferece uma interface com o método run chamada Runnable

Mais natural que crair uma classe com uma única função, é escrever apenas esta função, certo?

public static void log(String message) {
    // ..
}
 
public static void save(String message) {
    // ..
}
 
public static void send(String message) {
    // ..
}

Também não é necessário escrever uma classe para coordenar a execução.

public static void execute(List<Runnable> tasks ) {
    tasks.forEach( Runnable::run );
}

E todos os comandos podem ser executados da mesma forma que antes.

List<Runnable> tasks = new ArrayList<>();
tasks.add(() -> log("Hi"));
tasks.add(() -> save("Cheers"));
tasks.add(() -> send("Bye"));
 
execute( tasks );

Fusco explica:

Aqui, o compilador Java automaticamente traduz as expressões Lambda sem parâmetros em classes anônimas que implementam a interface Runnable, o que permite que a lista de comandos seja armazenada em uma lista.

Recomendação de Fusco traduzida para C#

Agora é hora de implementar as recomendações de Mario Fusco em C#!

Primeiro, não precisamos criar uma interface. Também não precisamos verificar se a BCL provê uma interface adequada também. Podemos usar delegates!

public delegate void Command();

Estamos definindo um delegate, mas poderíamos usar uma Action.

Podemos escrever métodos simples, da mesma forma como Mario fez.

public static void Log(string message)
{
    // ..
}

public static void Save(string message)
{
    // ..
}

public static void Send(string message)
{
    // ..
}

Também podemos executar facilmente:

var commands = new List<Command>
{
    () => Log("Hi"),
    () => Save("Cheers"),
    () => Send("Bye")
};

commands.ForEach(command => command());

Em uma abordagem ainda mais funcional, podemos converter os comandos em HOF.

public static Command Log(string message)
    => () => { /* .. */ };

public static Command Save(string message)
    => () => { /* .. */ };

public static Command Send(string message)
    => () => { /* .. */ };

Esta pode ser uma boa ideia, especialmente se precisamos alguma configuração. De qualquer forma, isso remove a necessidade de usar Lmbdas para criar a lista de comandos.

var commands = new List<Command>
{
    Log("Hi"),
    Save("Cheers"),
    Send("Bye")
};

commands.ForEach(command => command());

Não precisamos criar uma classe para implementar o padrão Command. Há alternativas mais simples em Java e em C#

Em breve, vamos falar sobre o padrão Strategy.

Compartilhe este insight:

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Mais insights para o seu negócio

Veja mais alguns estudos e reflexões que podem gerar alguns insights para o seu negócio:

Geralmente, quanto maior é uma organização, menos relevante é a qualidade das ideias ou das iniciativas. Nelas, o fundamental para...
Pimenta nos olhos dos outros é refresco. Quando não é pela satisfação sádica proporcionada pela dor alheia é pelo alívio...
Utilizar o Google Tag Manager (GTM) em uma Single-Page Aplication (SPA) exige alguns cuidados. Neste post, apresento algumas lições aprendidas...
Confesso que estava sentindo falta de escrever em primeira pessoa. Há tempos, publicando apenas nos sites da EximiaCo, não encontrava,...
Este exemplo é inspirado no livro do Ayende Se você deseja aprender RavenDB, recomendo que se inscreva no RavenDB bootcamp...
No último sábado, comprei um “toca-discos”. A experiência de ouvir um LP é algo bem diferente de quase tudo que...