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:

Outro dia, meu amigo Giovanni Bassi compartilhou o seguinte tuíte: Ele e eu concordamos em discordar muitas vezes. Esta é...
Parsing large files is a recurring and challenging task. Right? It is too easy to write slow code that consumes...
Há tempos que percebo em mim a ocorrência de um padrão recorrente. Não acho que ele seja exclusividade minha, mas,...
Aprendemos que a priorização das atividades deve ser feita, invariavelmente, pelo time do negócio. Na prática, entretanto, em nosso time,...
Recebi um bocado de feedback positivo para minhas palestras no Devxperience deste ano. Muita gente mandou e-mails solicitando, principalmente, os...
A ausência de padrões leves para externar (seja para documentação ou na elaboração) a arquitetura de um software sempre é...
× Precisa de ajuda?