커맨드 패턴?
- 커맨드 패턴은 객체의 행위(메서드)를 클래스로 만들어 캡슐화하는 패턴이다.
다시 말하자면, 어떤 객체(A)에서 다른 객체(B)의 메서드를 실행하려면 객체(B)를 참조하고 있어야 하는 의존성이 발생하게 된다. 이를 방지하기 위한 패턴이다.
예를 들어, 기가 지니(GiGA Genie)를 생각해 보도록 하자. 기가 지니에게 "TV 틀어줘" 라고 하면, 실제로 기가 지니가 TV를 틀어주는 서비스가 있다. 이를 사용하는 사용자(User), 기가 지니(GiGa_Genie), TV(TV)를 클래스로 만들어 보자. 그러면 GiGa_Genie는 TV를 켜기 위해서 TV객체를 참조해야 한다.
//TV 클래스 구현 public class TV { //TV를 켜는 메서드 public void powerOn() { System.out.println("TV on"); } } //GiGa_Genie 클래스 구현 public class GiGa_Genie { //TV객체를 참조하기 위한 변수 지정 private TV tv; //TV객체를 참조하여 GiGa_Genie 생성자 정의 public GiGa_Genie (TV tv) { this.tv = tv; } //TV를 킨다고 음성으로 User에게 알려줄 메서드(TV의 객체 참조) public void talk() { tv.powerOn(); } } //User 클래스 구현(main클래스) public class User { public static void main(String[] args) { //TV 객체 생성 TV tv = new TV(); //GiGa_Genie 객체 생성 GiGa_Genie giga_Genie = new GiGa_Genie(); //GiGa_Genie 클래스에 있는 메서드 실행 giga_Genie.talk(); } }
하지만, GiGa_Genie는 TV를 켜주는 기능만 있는것은 아니다. 예를들어 버스(Bus) 정보도 알려준다. 그렇다면, Bus 클래스를 만들고, GiGa_Genie 클래스에서 Bus 객체를 참조하도록 해보자.
//TV 클래스 구현 public class TV { //TV를 켜는 메서드 public void powerOn() { System.out.println("TV on"); } } //Bus 클래스 구현 public class Bus { //Bus 정보를 알려주는 메서드 public void showInfo() { System.out.println("Bus Info"); } } //GiGa_Genie 클래스 구현 public class GiGa_Genie { //TV와 Bus를 담을 객체를 배열로 저장(즉, 0이면 TV를 켜고, 1이면 Bus정보를 알려준다) private static String[] modes = {"TV","Bus"}; //TV와 Bus 객체를 참조하기 위한 변수 선언 private Tv tv; private Bus bus; //어떤 mode를 사용할 지를 담을 변수 private String mode; //GiGa_Genie 생성자 선언(TV와 Bus를 참조한) public GiGa_Genie(TV tv, Bus bus) { this.tv = tv; this.bus = bus; } //어느 mode를 사용 할지 위한 메서드(Setter) public void setMode (int index) { this.mode = modes[index]; // setMode 메서드는 index를 받아 0이면 TV를 켜고, 1이면 Bus정보를 알려주는 mode로 설정해준다. } //TV를 켜거나 Bus 정보를 알려줄 메서드 (조건문/반복문으로 작성) public void talk() { switch (this.mode) { case "TV" : this.tv.powerOn(); case "Bus" : this.bus.showInfo(); } } } //User 클래스 구현(main클래스) public class User { public static void main(String[] args) { //TV,Bus 객체 생성 TV tv = new TV(); Bus bus = new Bus(); //GiGa_Genie 객체 생성 GiGa_Genie giga_Genie = new GiGa_Genie(tv,bus); //GiGa_Genie 클래스에 있는 메서드 실행 giga_Genie.setMode(0); giga_Genie.talk(); //System.out.println("==구분선=="); giga_Genie.setMode(1); giga_Genie.talk(); } } //결과값 TV on ==구분선== Bus Info
GiGa_Genie에게 mode 설정을 통해, 모드가 0이면 TV를 켜고, 1이면 Bus 정보를 알려주도록 하였다. GiGa_Genie 클래스는 TV를 켜거나 Bus 정보를 알려주기 위해 이 둘의 객체를 참조해야한다. 만약, GiGa_Genie의 기능이 수백가지라고 가정해보자. 그러면 각 객체의 프로퍼티(Setter/Getter 메서드)는 점점 늘어날 것이고, 기존 talk()메서드에서 분기가 늘어날 것이다. 즉, SOLID의 원칙중 O에 위배된다.
1) Command 인터페이스 구현
//Command 인터페이스 public interface Command { //메서드를 실행시킬 추상 메서드 public void doing(){} }
2) 각 클래스의 Command 클래스 구현(원래 클래스에는 그대로 메서드 구현)
//TV 클래스 구현 public class TV { //TV를 켜는 메서드 public void powerOn() { System.out.println("TV on"); } } //TV를 켜는 Command클래스 (feat.interface) public TVonCommand implements Command { //TV 객체를 참조할 변수 private TV tv; //TVonCommand 생성자(TV 객체를 참조) public TVonCommand(TV tv) { this.tv = tv; } //인터페이스 추상 메서드 구현 public void doing() { tv.powerOn(); } } //Bus 클래스 구현 public class Bus { //Bus 정보를 알려주는 메서드 public void showInfo() { System.out.println("Bus Info"); } } //Bus 정보를 알려주는 Command클래스 (feat.interface) public BusInfoCommand implements Command { //Bus 객체를 참조할 변수 private Bus bus; //BusInfoCommand 생성자(Bus 객체를 참조) public BusInfoCommand(Bus bus) { this.bus = bus; } //인터페이스 추상 메서드 구현 public void doing() { bus.showInfo(); } }
3) GiGa_Genie 클래스의 talk() 메서드를 Command인터페이스의 추상 메서드를 실행하도록 구현
//GiGa_Genie 클래스 구현 public class GiGa_Genie { //Command 인터페이스를 담을 변수 선언 private Command command; //어느 Command를 실핼할 지 결정할 Setter메서드 public void setCommand(Command command) { this.command = command; } //talk() 메서드 구현 public void talk() { //Command 인터페이스의 추상 메서드 실행 command.doing(); } }
4) 이 코드를 실행시킬 User 클래스 구현(main클래스)
public class User { public static void main(String[] args) { //TV와 Bus 객체 생성 TV tv = new TV(); Bus bus = new Bus(); //Command 인터페이스를 통한 메서드 구현 Command TV_Command = new TVonCommand(tv); Command Bus_Command = new BusInfoCommand(bus); //GiGa_Genie 객체 생성 GiGa_Genie giga_Genie = new GiGa_Genie(); //TV를 킨다 giga_Genie.setCommand(TV_command); giga_Genie.doing(); //Bus정보를 얄려준다 giga_Genie.setCommand(Bus_command); giga_Genie.doing(); } } //결과값 TV on ==구분선== Bus Info
장점
- 객체간의 의존성을 제거해준다 => TV,Bus의 메서드를 GiGa_Genie에서 실행시키려면 TV,Bus의 객체를 참조해야 하는데(의존성이 높아진다), 이를 제거해준다.
- 단일 책임의 원칙(SOLID의 S)에 부합하다 => TV,Bus와 GiGa_Genie를 분리할 수 있다.
- 개방-폐쇄의 원칙(SOILD의 O)에 부합하다 => GiGa_Genie에 더 많은 기능이 추가된다고 하더라도 코드의 수정 없이 추가가 가능하다
단점
- 설계 구조가 복잡하다 => GiGa_Genie의 기능이 1만가지가 된다고 가정한다면 너무 많은 코드가 쓰여지고 그만큼 복잡해지게 된다.