From GoF to Lambdas – The Command Pattern

Last year, Mario Fusco wrote a blog post series questioning the way Java developers are implementing the Gang of Four (GoF) patterns. He shared a functional approach, which is easier to read and maintain.

I will do my best to provide a C# version of Mario’s recommendations expanding some examples. Let’s start with the Command pattern.

The Command Pattern – how it is commonly adopted today

The Command Pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time.

In Java, this is commonly implemented defining an interface:

interface Command {
    void run();
}

At this point, the developer can provide different command implementations.

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

The “consumer” does not have to know about the details of the command execution. Some implementations will just affect the application state, other could save data on disk, for example.

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

In some cases, commands could be used to coordinate work with another components/applications.

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

It is a good practice to have something to coordinate the execution of one or more instances of this commands.

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

Finally it is possible to create a list of the commands we want to run

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

Commands, implemented in this way, are just functions wrapped into objects.

The Command Pattern – Mario’s recommendation

The FunctionalInterface concept could make the Command pattern implementation a lot less verbose.

@FunctionalInterface
interface Command {
    void run();
}

If you are not familiar with the @FunctionalInterface annotation, they are used with Single Abstract Method interfaces – like Command. Functional interfaces can be represented with a simple lambda expression or a method reference. Java already provides a functional interface with a run method named Runnable

More natural than creating a class wrapping a single function is to write just that single function, right?

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

We don’t need an Executor class as well:

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

And all the commands could be executed just as before.

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

Mario explains:

Here the Java compiler automatically translates the lambdas with no arguments and invoking the void static methods executing the actions formerly wrapped in commands into anonymous implementation of the Runnable interface thus allowing to collect them in a List of Runnables.

Mario’s recommendation translated to C#

Now, it is time to apply Mario’s recommendation using C#. Let’s go!

First of all, we don’t need to create an interface. We don’t need to check if the BCL provides an interface neither. We have delegates!

public delegate void Command();

Here we are defining a delegate, but we could use Action with no prejudice.

We can use simple methods just like Mario did.

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

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

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

We can also execute it easily:

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

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

In a more “functional” approach, we could convert are functions into HOF.

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

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

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

This could be a good idea, especially if we want to perform some complex setup. Anyway, it removes the need of using lambdas when creating a command list.

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

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

We don’t need to create a class to implement the command pattern. There are simpler alternatives in Java *and* in C#.

Next time, let’s talk about the strategy pattern.

Compartilhe este insight:

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:

Você ainda acredita em estimativas? Nós, não. Embora aceitemos que ter uma boa ideia de esforço e prazo sejam diferenciais...
Nessa última semana, Fernando Neiva apresentou um compilado de nossas lições aprendidas implementando Kanban na Guiando. Aqui, compartilhamos o registro...
Há muito valor em saber como fazer as coisas. Entretanto, antes, é preciso saber o porquê. Conheço excelentes profissionais, profundos...
Quando estamos desenvolvendo aplicações distribuídas, não devemos nos perguntar se teremos problemas de conectividade. No lugar disso, devemos nos perguntar...
Gosto bastante da abordagem de Caitie McCaffrey para explicar sagas. Neste post, me inspiro na linha de raciocínio dela para...
Implementing a good caching strategy is fundamental to achieve good performance. Besides that, it is not a trivial task. There...
Masterclass

O Poder do Metamodelo para Profissionais Técnicos Avançarem

Nesta masterclass aberta ao público, vamos explorar como o Metamodelo para a Criação, desenvolvido por Elemar Júnior, pode ser uma ferramenta poderosa para alavancar sua carreira técnica em TI.

Crie sua conta

Preencha os dados para iniciar o seu cadastro no plano anual do Clube de Estudos:

Crie sua conta

Preencha os dados para iniciar o seu cadastro no plano mensal do Clube de Estudos:

× Precisa de ajuda?