ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java Design Pattern - 커맨드 패턴(Command Pattern)
    Language/Java Design Pattern 2022. 11. 20. 20:43

    커맨드 패턴(Cammand Pattern)

    커맨드 패턴이란?

    커맨드 패턴은 객체의 행위(메서드)를 클래스로 만들어 캡슐화하는 패턴이다.

    즉, 어떤 객체(A)에서 다른 객체(B)의 메서드를 실행하기 위해서는 그 객체(B)를 참조하고 있어야 하는 의존성이 발생한다.

    하지만, 커맨드 패턴을 적용하면 이러한 의존성을 제거할 수 있다. 또한, 기능이 수정되거나 변경이 일어날 때, A 클래스 코드 수정할 필요 없이 기능에 대한 클래스를 정의하면 되기 때문에, 시스템이 확장성이 있으면서 유연해진다.

     

    커맨드 패턴의 사용 이유

    구글 홈이라고 "OK Google 히터 틀어줘"라고 하면, 히터를 틀어주는 실제 구글 서비스가 존재한다. 구글 홈을 사용하는 사용자를 Client, 구글 홈을 OKGoogle, 히터를 Heater 클래스로 정의해보겠다.

     

    public class Heater {
        public String powerOn() {
            return "Heater On";
        }
    }
    public class OKGoogle {
        private Heater heater;
        
        public OKGoogle(Heater heater) {
            this.heater = heater;
        }
    
        public String talk() {
            heater.powerOn();
        }
    }
    public class Client {
        public static void main(String[] args) {
            Heater heater = new Heater();
            OKGoogle okGoogle = new OKGoogle(heater);
            System.out.println(okGoogle.talk);   // "Heater ON" 출력
        }
    }

    OKGoogle은 히터를 켜기 위해서 Heater 객체를 참조해야 한다. 이를 코드로 표현하면 위와 같다.

     

    OKGoogle에서 히터를 키는 기능 말고, 램프를 키는 기능을 추가하고 싶다면 어떻게 해야 할까?

    Lamp 클래스를 정의하고, OKGoogle 클래스에서 Lamp 객체를 참조하도록 해야 한다. 물론 기존의 Heater 기능 또한 있어야 한다.

     

    public class Heater {
        public String powerOn() {
            return "Heater ON";
        }
    }
    public class Lamp {
        public String turnOn() {
            return "Lamp ON";
        }
    }
    public class OKGoogle {
        public static String[] modes = {"heater", "lamp"};
        
        private Heater heater;
        private Lamp lamp;
        private String mode;
        
        public OkGoogle(Heater heater, Lamp lamp) {
            this.heater = heater;
            this.lamp = lamp;
        }
        
        public void setMode(int modeIndex) {
            this.mode = modes[modeIndex];
        }
        
        public String talk() {
            switch (this.mode) {
                case "heater":
                    return heater.powerOn();
                case "lamp":
                    return lamp.turnOn();
                default:
                    return "모드를 설정해 주세요.";
            }
        }
    }
    public class Client {
        public static void main(String[] args) {
            Heater heater = new Heater();
            Lamp lamp = new Lamp();
            OKGoogle okGoogle = new OKGoogle(heater, lamp);
            
            okGoogle.setMode(0);
            System.out.println(okGoogle.talk());   // "Heater ON" 출력
            
            okGoogle.setMode(1);
            System.out.println(okGoogle.talk());   // "Lamp ON" 출력
        }
    }

    OKGoogle의 mode 설정을 통해, mode가 0이면 히터를 틀고, 1이면 램프를 켜도록 가정했다.

    OKGoogle은 히터를 틀고, 램프를 켜기 위해서 Heater, Lamp 객체를 참조해야 하기 때문에, OKGoogle의 기능이 많아질수록 객체 프로퍼티는 더욱 늘어날 것이고, 기존의 talk() 메서드 내 분기 또한 늘어날 것이다.

     

    커맨드 패턴의 적용

    위에서 언급한 문제점을 해결하기 위해 커맨드 패턴을 적용해보겠다.

     

     

    우선, OKGoogle이 할 수 있는 기능들을 클래스(HeaterOnCommand, LampOnCommand)로 만들어서 각 기능들을 캡슐화한다.

    그리고 OKGoogle 클래스의 talk() 메서드에서 heater.powerOn(), lamp.turnOn()와 같은 기능들을 직접 호출하는 것이 아닌, 캡슐화한 Command 인터페이스의 메서드를 호출하도록 한다.

     

    public interface Command {
        public String run();
    }

     

    우선, 인터페이스를 정의한다.

     

    public class HeaterOnCommand implements Command {
        private Heater heater;
        
        public HeaterOnCommand(Heater heater) {
            this.heater = heater
        }
        
        @Override
        public String run() {
            return heater.powerOn();
        }
    }
    public class Heater {
        public String powerOn() {
            return "Heater ON";
        }
    }

    Heater를 켜는 명령을 클래스화한 HeaterOnCommand 클래스를 정의하고, Heater 클래스는 그대로 히터를 켜는 powerOn() 메서드를 정의한다.

     

    public class LampOnCommand implements Command {
        private Lamp lamp;
        
        public LampOnCommand(Lamp lamp) {
            this.lamp = lamp;
        }
        
        public String run() {
            return lamp.turnOn();
        }
    }
    public class Lamp {
        public String turnOn() {
            return "Lamp ON";
        }
    }

    마찬가지로, Lamp를 켜는 명령을 클래스화하여 LampOnCommand 클래스를 정의하고, Lamp 클래스는 그대로 램프를 켜는 lampOn() 메서드를 정의한다.

     

    public class OKGoogle {
        private Command command;
        
        public void setCommand(Command command) {
            this.command = command;
        }
        
        public String talk() {
            return command.run();
        }
    }

    OKGoogle 클래스의 talk() 메서드에는 Command 인터페이스의 run() 메서드를 호출하여, 명령을 실행한다.

     

    만약 OKGoogle에 TV를 틀어줘 기능이 추가된다면, TVOnCommand 클래스를 추가하면 되므로, OCP에 위배되지 않으면서 기능을 추가할 수 있다.


    출처

    https://victorydntmd.tistory.com/295

     

    728x90

    댓글

Designed by Tistory.