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.