Java 디자인 패턴-03.Command

(Java 디자인 패턴 스터디 모집 중 : https://github.com/bluedskim/javaDesignPatterns)

Command Pattern : Real-World Analogy (출처:https://refactoring.guru/design-patterns/command)

해결하려는 문제

  1. view(presentation)과 비즈니스 로직의 직접 연관을 제거(행위가 소스에 하드코딩되어 있다면 런타임에 추가/변경이 불가능)

용도/목적

  1. 조건문이 많은 복잡한 로직을 간단하게
  2. invoker와 receiver 사이에 command를 두어 직접 연관을 제거하여 새로운 command가 추가되더라도 invoker를 수정할 필요가 없음

특징

  1. 행위behavioral 패턴
  2. 눈에 보이지 않는 무형의 개념(행위)도 객체화 할 수 있다.

고려사항

  1. command용 interface(TextFileOperation.java)는 @FunctionalInterface로 할 수 있다.

클래스 다이어그램

Command Method 패턴(김동석)

소스

  1. client : command를 초기화하고 invoker를 호출

    • TextFileOperationExecutorWithoutCommandTest.java : command를 사용하지 않은 샘플 테스트용
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      
      package net.dskim.desingpattern.command;
      
      import static org.junit.jupiter.api.Assertions.assertEquals;
      
      import org.junit.jupiter.api.Test;
      
      import lombok.extern.slf4j.Slf4j;
      
      @Slf4j
      public class TextFileOperationExecutorWithoutCommandTest {
      	@Test
      	public void executeOperationTest() {
      		TextFileOperationExceutorWithoutCommand textFileOperationExecutor = new TextFileOperationExceutorWithoutCommand();
      
      		String openTextFileResult = textFileOperationExecutor.execute(new TextFile("file1.txt"), "open");
      		log.info("openTextFileResult={}", openTextFileResult);
      		assertEquals("Opening file file1.txt", openTextFileResult);
      
      		String saveTextFileResult = textFileOperationExecutor.execute(new TextFile("file2.txt"), "save");
      		log.info("saveTextFileResult={}", saveTextFileResult);
      		assertEquals("Saving file file2.txt", saveTextFileResult);
      	}
      }
    • TextFileOperationExecutorWithCommandTest.java : command를 사용한 샘플 테스트용
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      
      package net.dskim.desingpattern.command;
      
      import static org.junit.jupiter.api.Assertions.assertEquals;
      
      import org.junit.jupiter.api.Test;
      
      import lombok.extern.slf4j.Slf4j;
      
      @Slf4j
      public class TextFileOperationExecutorWithCommandTest {
      	@Test
      	public void executeOperationTest() {
      		TextFileOperationExecutorWithCommand textFileOperationExecutor = new TextFileOperationExecutorWithCommand();
      
      		String openTextFileOperationResult = textFileOperationExecutor.executeOperation(new OpenTextFileOperation(new TextFile("file1.txt")));
      		log.info("openTextFileOperationResult={}", openTextFileOperationResult);
      		assertEquals("Opening file file1.txt", openTextFileOperationResult);
      
      		String saveTextFileOperation = textFileOperationExecutor.executeOperation(new SaveTextFileOperation(new TextFile("file2.txt")));
      		log.info("saveTextFileOperation={}", saveTextFileOperation);
      		assertEquals("Saving file file2.txt", saveTextFileOperation);
      		
      		String udateTextFileOperation = textFileOperationExecutor.executeOperation(new UpdateTextFileOperation(new TextFile("file2.txt")));
      		log.info("udateTextFileOperation={}", udateTextFileOperation);
      		assertEquals("Updating file file2.txt", udateTextFileOperation);
      	}
      }
  2. invoker : command를 받아 interface에 정의된 메소드를 호출하고 필요 시 이력 저장용 list에 저장

    • TextFileOperationExecutorWithCommand.java
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      
      package net.dskim.desingpattern.command;
      
      import java.util.ArrayList;
      import java.util.List;
      
      /**
       * command 패턴 샘플 invoker 클래스
       * 새로운 처리가 추가 되더라도 이 소스를 수정할 필요 없음
       */
      public class TextFileOperationExecutorWithCommand {
      	/**
      	 * 실행 이력 저장용. 이 샘플에서는 불필요
      	 */
      	private final List<TextFileOperation> textFileOperations = new ArrayList<>();
      
      	public String executeOperation(TextFileOperation textFileOperation) {
      		textFileOperations.add(textFileOperation);
      		return textFileOperation.execute();
      	}
      }
  3. receiver : 실제 로직이 정의된 클래스

    • TextFile.java
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      
      package net.dskim.desingpattern.command;
      
      public class TextFile {
      	private String name;
      
      	public TextFile(String name) {
      		this.name = name;
      	}
      
      	public String open() {
      		return "Opening file " + name;
      	}
      
      	public String save() {
      		return "Saving file " + name;
      	}
      	public String update() {
      		return "Updating file " + name;
      	}
      }
  4. command : command 객체

    • TextFileOperation.java : command용 interface
      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      package net.dskim.desingpattern.command;
      
      /**
       * command용 interface
       */
      @FunctionalInterface
      public interface TextFileOperation {
      	String execute();
      }
    • OpenTextFileOperation.java : 파일 open하기 command
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      
      package net.dskim.desingpattern.command;
      
      /**
       * 파일열기 command
       */
      public class OpenTextFileOperation implements TextFileOperation {
      	private TextFile textFile;
      
      	public OpenTextFileOperation(TextFile textFile) {
      		this.textFile = textFile;
      	}
      
      	@Override
      	public String execute() {
      		return textFile.open();
      	}
      }
    • SaveTextFileOperation.java : 파일 save하기 command
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      
      package net.dskim.desingpattern.command;
      
      /**
       * 파일저장 command
       */
      public class SaveTextFileOperation implements TextFileOperation {
      	private TextFile textFile;
      
      	public SaveTextFileOperation(TextFile textFile) {
      		this.textFile = textFile;
      	}
      
      	@Override
      	public String execute() {
      		return textFile.save();
      	}
      }

참고