본문 바로가기
Programming/OOP

[Design Pattern] Command Pattern, 커맨드 패턴

by TinKerBellBass 2022. 5. 6.
728x90
반응형

커맨드 패턴 하면 서블릿 / JSP 시절 컨트롤러 만들 때
컨트롤러를 다 커맨드로 만들고 프론트 컨트롤러에서 분기 처리하던 생각이 난다.

interface Command {
	excute();
}

class GetUserController implements Command {
	excute() {... userService.get()...}
}

class FrontController extends HttpServlet {
    public void service(HttpServletRequest request, HttpServletResponse response) {
        final String uri = request.getURI()
        final String method = request.getMethod()
        
        if (uri.equals("/users") && method.equals("get")) {
        	final GetUserController getUser = new GetUSerController()
        	getUser.execute()
        }
    }
}

대충 이런 느낌이었나?? 국비교육할 때 서블릿 / JSP 로 게시판 만든 거 생각나네...
지금 코드 대충 적고 보니 인보커가 빠져있네. 그때도 인보커 없이 코딩했었나? 기억이...

Command Pattern

커맨드 패턴에는 4명의 등장인물이 있다.

Client, 클라이언트 인보커에 커맨드를 세팅하고 커맨드 실행을 위임
Invoker, 인보커 커맨드를 실행
Command, 커맨드 수행해야 할 작업들을 캡슐화 하여 가지고 있으며, 그 작업들을 실행
Receiver, 리시버 수행해야 할 작업

클라이언트에서 실행을 위임받아 여러 작업을 처리하는 객체가 인보커인데,
인보커가 직접 리시버들을 구성으로 가지고 호출해서 사용하게 되면 인보커와 리시버 사이에 강한 결합이 생기게 된다.
이렇게 되면 인보커는 구성으로 가지고 있는 리시버의 작업만 행할 수 있으므로 유연성이 떨어진다.
여기서 커맨드가 중간에 끼어들어 인보커와 리시버의 결합을 끊어 버린다.
커맨드 패턴은 커맨드를 사용해서 의존성을 역전시키고 유연한 구조를 만든다.

커맨드는 수행해야 할 작업들을 캡슐화하여 가지고 있기 때문에 커맨드를 세팅한 인보커 인스턴스를 만들고,
인보커 인스턴스를 큐에 집어넣은 후 꺼내서 커맨드를 실행하는 등의 설계가 가능하다.

커맨드 패턴의 단점은 커맨드를 대량으로 생산해야 하는 경우인데 람다를 이용해서 해결할 수 있다.
자바의 경우 언어에서 지원해 주는 Functional Interface 를 사용하면 커맨드 인터페이스를 만들 필요도 없다.
인보커는 람다를 파라미터로 받고, 클라이언트는 람다로 커맨드를 만들어서 인보커에게 전달하면 되는 것이다.

커맨드 패턴의 단골손님은 자바의 스레드의 Runnable 인터페이스이다.

// 익명 클래스
new Thread(new Runnable() {...})
// 람다
new Thread(() -> {...})

Runnable 인터페이스가 커맨드 인터페이스, Thread 가 인보커이다.
그리고 스프링 기동시에 초기 작업 등에 사용되는 CommandLineRunner 도 커맨드 패턴으로 구현되어 있다.

Command Pattern Class Diagram

커맨드는 파라미터를 하나 받아서 아무것도 반환하지 않는 자바가 가지고 있는
Consumer Functional Interface 를 이용해서 람다로 구현하였다.
코틀린, 고, 타입스크립트의 경우 파리미터에 직접 람다 타입(함수 타입)을 설정할 수 있기 때문에
Consumer 같은 귀찮은 인터페이스가 불필요하다.

Transfer 가 인보커, Consumer 가 커맨드 인터페이스, AbcBank 와 XyzBank 가 리시버이다.
커맨드 테스트는 클라이언트로 커맨드 구상 인스턴스를 람다로 만들어 인보커인 Transfer 에게 넘기고 처리를 위임한다.
전체 소스는 https://github.com/Jongwon-Hyun/design_pattern

Strategy Pattern VS. Command Pattern

둘 다 그때 그 때 필요한 구상 객체를 받아서 사용하기 때문에 비슷한 패턴으로 인식된다.
차이점으로는 전략 패턴은 주입하는 구상 객체(전략 객체)가 리시버 없이 직접 필요한 작업을 수행하며
커맨드 패턴은 구상 객체(커맨드 객체)가 리시버들에게 필요한 작업들을 위임해서 수행한다.
그래서 전략 패턴은 필요한 작업들(전략 객체)이 통일되어 있고 커맨드 패턴은 작업들(리시버)이 통일되어 있지 않다.

코드적인 관점보다는 사용목적에 관점을 두고 차이점을 이해하는 것이 더 좋다고 생각하는데,
전략 패턴은 어떤 전략을 사용할 것인가, HOW 에 초점이 맞춰져 있고,
커맨드 패턴은 어떤 작업을 할 것인가, WHAT 에 초점이 맞춰져 있다.
그래서 커맨드는 수행할 작업들(리시버)을 캡슐화해서 가지고 있는 것이다.
구체적으로 예를 들어보면
"칼로 싸울 것인가, 총으로 싸울 것인가"는 전략 패턴의 관점이고
"상단 주먹을 날리고 발을 건 다음에 걷어 차기"는 커맨드 패턴의 관점인 것이다.

728x90
반응형

댓글