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.

Este post tem um comentário

Deixe uma resposta