Skip to content

Command

The goal of the Command behavioral pattern is to separate objects that send requests from objects that accept their results. It is done by introducing the command object for this process. Optionally, the command may also undo the result of such a process.

By wrapping the process execution into a separate object, we keep the SOLID principles, removing the potential strong coupling between the sender and the recipient of the request result.

Construction and Example

The main element to create is a common object representing the process execution. Optionally, it may be possible to undo the execution of the command.

The objects needed to execute the command usually come in the implementation constructor.

The example below uses the Command interface, along with two implementations: ChangeFileNameCommand and RemoveEmptyLinesCommand. It is possible to undo the invocation of the ChangeFileNameCommand command with thecancel method.

public interface Command {
  void apply();
  void cancel();
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class JavaFile {
  private String fileName;
  private String className;
  private List<String> linesContent = new ArrayList<>();

  public void addLine(final String line) {
    linesContent.add(line);
  }

  @Override
  public String toString() {
    return "JavaFile{" +
        "fileName='" + fileName + '\'' +
        ", className='" + className + '\'' +
        ", linesContent=" + String.join("\n", linesContent) +
        '}';
  }
}
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ChangeFileNameCommand implements Command {

  private final JavaFile javaFile;
  private final String newName;
  private String previousName = null;

  public ChangeFileNameCommand(final JavaFile javaFile, final String newName) {
    this.javaFile = javaFile;
    this.newName = newName;
  }

  @Override
  public void apply() {
    if (javaFile.getFileName().equals(newName)) {
      log.warn("Cannot change file name to same one");
    } else {
      previousName = javaFile.getFileName();
      javaFile.setFileName(newName);
      javaFile.setClassName(newName.substring(0, newName.length() - 5)); // lack of validation! + simplified solution - we dont change classname in lines field
      log.info("File name changed to " + newName);
    }
  }

  @Override
  public void cancel() {
    if (previousName == null) {
      log.warn("Cannot cancel command");
    } else {
      javaFile.setFileName(previousName);
      javaFile.setClassName(previousName);
      previousName = null;
    }
  }
}
import java.util.List;
import java.util.stream.Collectors;

public class RemoveEmptyLinesCommand implements Command {

  private final JavaFile javaFile;

  public RemoveEmptyLinesCommand(final JavaFile javaFile) {
    this.javaFile = javaFile;
  }

  @Override
  public void apply() {
    final List<String> nonEmptyLines = javaFile.getLinesContent().stream()
        .filter(line -> !line.trim().isEmpty())
        .collect(Collectors.toList());
    javaFile.setLinesContent(nonEmptyLines);
  }

  @Override
  public void cancel() {
    throw new UnsupportedOperationException("Cancelling this operation is not possible ATM");
  }
}

The client using the execution results of the commands:

import java.util.List;

public class FileOperations {
  public static void main(String[] args) {
    final JavaFile javaFile = new JavaFile("Commands.java", "Commands",
        List.of("Command {", " ", "private String content;", "}"));
    final Command changeFileNameCommand = new ChangeFileNameCommand(javaFile, "UpdatedCommands.java");
    final Command removeEmptyLinesCommand = new RemoveEmptyLinesCommand(javaFile);

    System.out.println(javaFile);

    changeFileNameCommand.apply();
    removeEmptyLinesCommand.apply();

    System.out.println(javaFile);

    changeFileNameCommand.cancel();

    System.out.println(javaFile);
  }
}

The result of the program will be:

JavaFile{fileName='Commands.java', className='Commands', linesContent=Command {

private String content;
}}
09:41:36.325 [main] INFO pl.sdacademy.command.ChangeFileNameCommand - File name changed to UpdatedCommands.java
JavaFile{fileName='UpdatedCommands.java', className='UpdatedCommands', linesContent=Command {
private String content;
}}
JavaFile{fileName='Commands.java', className='Commands.java', linesContent=Command {
private String content;
}}