[Java] 디자인 패턴, Command 패턴 알아보자

     

    💭 Command 패턴이란?

    • 행동 디자인 패턴이다.
    • 요청을 객체의 형태로 캡슐화하여 사용자가 요청을 보내는 측요청을 수행하는 측분리하는 패턴
    • 요청 실행, 취소, 로깅, 트랜잭션 등의 기능등 확장이 가능하다.
    • 복잡한 명령 시퀀스나 작업을 관리할 때 매우 유용하다.

     
    Command 패턴은 디자인 패턴 중 가장 어려웠던 패턴 중 하나였습니다.
    그 이유가 왜 굳이 이렇게까지 분리를 해야 하는가???라는 부분에서 시작했습니다.
     
    얼핏 봤을 때는 Strategy패턴과 유사하면서도 또 다르게는 이렇게 비효율적으로 코드를 만들어야 하는 이유를 몰라서였습니다.  먼저 Command 패턴의 구성요소를 먼저 소개해드리겠습니다.
     
     
     

    💭 구성요소

    요소 설명
    Command
    • 명령을 나타내는 인터페이스
    • 명령을 실행하는데 필요한 메서드 execute() 를 선언
    Concrete Command
    • Command 인터페이스를 구현
    • 특정한 행동을 캡슐화하는 클래스
    • execute()  메소드를 통해 특정한 작업을 수행하도록 구현
    Invoker
    • Command 객체를 요청하고 이를 사용하는 클래스
    • 명령을 실행할 적절한 시점에 execute() 메소드를 호출
    Receiver
    • Concrete Command   에 의해 특정한 요청에 대해 실제로 동작을 수행하는 클래스
    Client
    • Concrete Command   객체를 생성하여 Invoker 에 할당
    • Receiver Concrete Command 에 연결할 책임을 지님
    Command패턴의 구성 형태

    그림으로 표현하자면 위와 같습니다.
    Main 함수는 실행하기 위해서는 Invoker를 반드시 수행해야하며, 실행 결과는 Reciver에 저장되게 됩니다.
     
     

    🧑‍💻 코드보기 ( 랜턴 켜기, 끄기 )

    🍀 Light ( Receiver )

    public class Light {
        private boolean status;
    
        public void change(boolean status) {
            this.status = status;
        }
    
        public boolean isStatus() {
            return status;
        }
    
    }

     
     

    🍀  Command ( Interface, Command )

    public interface Command {
    
        void isGetLight();
    
        void execute();
    }

     
     

    🍀  ConcreteCommandOn ( ConcreteCommand )

    public class ConcreteCommandOn implements Command{
        Light light;
    
        public ConcreteCommandOn(Light light) {
            this.light = light;
        }
    
        @Override
        public void isGetLight() {
            System.out.println("상태: "+this.light.isStatus());
        }
    
        @Override
        public void execute() {
            this.light.change(true);
        }
    }

     
     

    🍀  ConcreteCommandOff ( ConcreteCommand )

    public class ConcreteCommandOff implements Command{
        Light light;
    
        public ConcreteCommandOff(Light light) {
            this.light = light;
        }
    
        @Override
        public void isGetLight() {
            System.out.println("상태: "+this.light.isStatus());
        }
    
        @Override
        public void execute() {
            this.light.change(false);
        }
    }

     
     

    🍀  Invoker ( Invoker )

    public class Invoker {
        Command command;
    
        public Invoker(Command command) {
            this.command = command;
        }
    
        public void status() {
            this.command.isGetLight();
        }
    
        public void change() {
            this.command.execute();
        }
    }

     
     

    🍀  Main ( Main 실행 )

    public class Main {
    
        public static void main(String[] args) {
    
            Light light = new Light();
    
            ConcreteCommandOn on = new ConcreteCommandOn(light);
            ConcreteCommandOff off = new ConcreteCommandOff(light);
    
            Invoker ink = new Invoker(on);
            ink.change();
    
            ink.status();
    
            ink = new Invoker(off);
            ink.status();
    
            ink.change();
            ink.status();
    
    
        }
    }

     

     

    🍀   실행 결과

     
     
     

    💭 Command 파헤치기

    ❓ 1. Command가 적용이 안 되어 있을 경우.

    Command가 없을 경우

     
    만약, Command패턴이 존재하지 않을 경우에는 다음과 같이 저장이 될 것 입니다.
    Main에서 바로 Receiver에 데이터 저장을 하게 되는데, 이는 보통의 getterSetter하고 동일한 경우 입니다.
     
     
     

    ❓ 2. Receiver와 Command만 존재할 경우

    Receiver와 Command만 존재할 경우.

    Invoker가 도저히 무엇하는 용도인지는 모르므로 없앴을 때의 Command의 모습 입니다.
     
    Main은 Receiver에게 바로 접근이 불가능한 상태 ( 캡슐화 )가 되었습니다.
    이때 우리는 디자인패턴답게, 동적바인딩 기술을 구현하게 되었을 때, 다음 그림과 같은 모습이 됩니다.
     
    ConcreteCommand1에게 접근했을 때와 ConcreteCommand2에게 접근했을 때의 Receiver에 도달하는 방식은 서로 달라지게 됩니다.
     
    여기서 의문이 생겼습니다.
    결과적으로 동적바인딩을 사용했을 뿐, 결과적으로는 Strategy처럼 여러 선택 중 하나를 선택하는 것과 별 차이점이 없었기  때문입니다.
     
     

    ❓ 3. Invoker에 중요성

    그러면 Command패턴이기 위해서는 가장 중요한 것은 무엇일까.
    결과적으로 말씀드리자면, Invoker가 될 수 있습니다.
     
    물론, 모든 객체가 중요 합니다. 그러나 Command가 유지되기 위해서는 Invoker의 존재가 있어야 완성이 될 수 있습니다.
     

    1. Command의 객체 또한 캡슐화가 되어야 한다.

    이는 command를 Receiver가 Command에게 주듯이, Command도 Invoker에게 건네줘야지 가능한 기능입니다.
     

    2. Command는 요청을 보내는 측과 요청을 수행하는 측이 나눠진다.

    Command의 유지되는 가장 중요한 이유입니다. Main에서 Invoker까지가 Command에게 요청을 보내는 측에 속하며, ConcreteCommand에서 Receiver까지 요청을 수행하는 측에 속합니다.
    이를 추상관계(상속)가 Command입니다.
     

    3. 요청 실행, 취소, 로깅, 트랜잭션 등의 기능등 확장이 가능

    Command의 가장 핵심 기능입니다.
    단순하게 데이터를 저장하는 것에 그치는 것이 아닌, 데이터를 저장하기 위해 존재합니다.
    즉, Invoker는 Command를 생성, 실행, 삭제 할 때 기록을 저장하는 역할을 담당합니다.
     
    그리고 Invoker로 인해서, Main함수의  Command의 접근을 막음으로써 캡슐화가 만들어집니다.

    Command의 역할 관리

     
     

    ❓ Strategy와 Command의 차이

    사실 이 부분이 가장 어려웠습니다.
    특히, Command의 동적바인딩을 구현을 연습할  때, Strategy와 너무 유사했기 때문입니다.
     

    둘의 차이는 사용 목적에 대한 차이

    - Command

    행동에 대한 결정만 지닌다.
    서울에서 인천으로 갈 때, 버스, 지하철, 택시 등 결과를 이루기 위한 행동을 우선시한다.
    눈 앞의 접시를 치우기 위해 왼손으로 치울 것인지, 오른손으로 치울 것인지 등이다.
     
    - Strategy
    효율적인 결과에 대한 우선순위가 우선이다.
    서울에서 인천으로 갈 때, 어떤 방법이 가장 빠를 것인지에 대한 선택이다.
    만약 Command로 인해 이미 선택이 이루어져있을 경우, 해당 범위 내에서 최대한의 효율을 만든다.
    눈 앞의 접시를 치울  때, 주변에 다른 또 다른 접시가 없는지 찾는다.
     
    만약, 택시를 타게 되었다면 어느 도로로 가는 것이 차가 안 막히는 길인지를 찾는다.
     
     
    Command는 '무엇을(What)'에 가깝고, Strategy는 '어떻게(How)'에 가깝다.
    Strategy는 어떻게 적을 쓰러트리는 것이 가장 효율적인지를 우선시 생각하며 ( MBTI의 J의 입장 )
    Command는 무엇을 해야 적을 쓰러트릴지를 생각하게 된다. ( MBTI의 P의 입장 )
     
    여기서 Command는 무엇을 했는가를 기록한다.
    언제(When)의 역할, Invoker가 있기 때문이다.
     
     
     

    🐳 마무리

    Command는 로그인 같은 환경에서 많이 사용 될 것 같다.
    처음에는 Strategy와 비슷하다고 생각했지만, Command를 배우고 나서는 Strategy보다는 Command에 가깝다는 생각이 들었다.
     
    로그인은 최근 여러가지 로그인 방식이 놓여져있다.
    일반 로그인, 간편로그인, 금융인증 로그인 등에 속한다.
     
    그러면 사용자의 선택에 따라서 로그인 방법이 달라질 것이고, 이는 Command와 ConcreteCommand가 수행하게 될 것이다. 물론, 결과적으로는 Receiver가 동작되면서 원래의 목적을 달성하게 된다.
     
    이 과정에서의 기록은 Invoker가 담당하여, 기록을 담당하게 된다.
     
    관리자인지, 일반인지 담당은 Strategy가 알아서 하겠지...

     
     
     

    반응형

    댓글

    Designed by JB FACTORY