176. Implementing the Command pattern

In a nutshell, the Command pattern is used in scenarios where a command is wrapped in an object. This object can be passed around without being aware of the command itself or the receiver of the command.

A classic implementation of this pattern consists of several classes. In our scenario, we have the following:

  • The Command interface is responsible for executing a certain action (in this case, the possible actions are move, copy, and delete). The concrete implementations of this interface are CopyCommand, MoveCommand, and DeleteCommand.
  • The IODevice interface defines the supported actions (move(), copy(), and delete()). The HardDisk class is a concrete implementation of IODevice and represents the receiver.
  • The Sequence class is the invoker of the commands, and it knows how to execute a given command. The invoker can act in different ways, but in this case, we simply record the commands and execute them in a batch when the runSequence() is called.

The Command pattern can be represented by the following diagram:

So, the HardDisk implements the actions that are given in the IODevice interface. As a receiver, the HardDisk is responsible for running the actual action when the execute() method of a certain command is called. The source code for IODevice is as follows:

public interface IODevice {
void copy();
void delete();
void move();
}

 

The HardDisk is the concrete implementation of IODevice:

public class HardDisk implements IODevice {

@Override
public void copy() {
System.out.println("Copying ...");
}

@Override
public void delete() {
System.out.println("Deleting ...");
}

@Override
public void move() {
System.out.println("Moving ...");
}
}

All concrete command classes implement the Command interface:

public interface Command {
public void execute();
}

public class DeleteCommand implements Command {

private final IODevice action;

public DeleteCommand(IODevice action) {
this.action = action;
}

@Override
public void execute() {
action.delete()
}
}

In the same manner, we have implemented CopyCommand and MoveCommand and skipped these for brevity purposes.

Furthermore, the Sequence class acts as the invoker class. The invoker knows how to execute the given command, but it doesn't have any clue about the command's implementation (it only knows the command's interface). Here, we record the commands in a List and execute those commands in a batch when the runSequence() method is called:

public class Sequence {

private final List<Command> commands = new ArrayList<>();

public void recordSequence(Command cmd) {
commands.add(cmd);
}

public void runSequence() {
commands.forEach(Command::execute);
}

public void clearSequence() {
commands.clear();
}
}

Now, let's see it at work. Let's execute a batch of actions on HardDisk:

HardDisk hd = new HardDisk();
Sequence sequence = new Sequence();
sequence.recordSequence(new CopyCommand(hd));
sequence.recordSequence(new DeleteCommand(hd));
sequence.recordSequence(new MoveCommand(hd));
sequence.recordSequence(new DeleteCommand(hd));
sequence.runSequence();

Obviously, we have a lot of boilerplate code here. Check out the classes of commands. Do we actually need all of these classes? Well, if we realize that the Command interface is actually a functional interface, then we can remove its implementations and provide the behaviors via lambdas (the command classes are just blocks of behavior, and so they can be expressed via lambdas), as follows:

HardDisk hd = new HardDisk();
Sequence sequence = new Sequence();
sequence.recordSequence(hd::copy);
sequence.recordSequence(hd::delete);
sequence.recordSequence(hd::move);
sequence.recordSequence(hd::delete);
sequence.runSequence();
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.35.255