CS_Step9 - 커맨드 패턴(Command Pattern)

장선웅·2022년 7월 20일
0

커맨드 패턴?

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

다시 말하자면, 어떤 객체(A)에서 다른 객체(B)의 메서드를 실행하려면 객체(B)를 참조하고 있어야 하는 의존성이 발생하게 된다. 이를 방지하기 위한 패턴이다.

1. 커맨드 패턴은 왜 사용할까?

예를 들어, 기가 지니(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에 위배된다.


2. 커맨드 패턴 구현 방법

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

3. 커맨드 패턴의 장/단점

장점

  1. 객체간의 의존성을 제거해준다 => TV,Bus의 메서드를 GiGa_Genie에서 실행시키려면 TV,Bus의 객체를 참조해야 하는데(의존성이 높아진다), 이를 제거해준다.
  2. 단일 책임의 원칙(SOLID의 S)에 부합하다 => TV,Bus와 GiGa_Genie를 분리할 수 있다.
  3. 개방-폐쇄의 원칙(SOILD의 O)에 부합하다 => GiGa_Genie에 더 많은 기능이 추가된다고 하더라도 코드의 수정 없이 추가가 가능하다

단점

  1. 설계 구조가 복잡하다 => GiGa_Genie의 기능이 1만가지가 된다고 가정한다면 너무 많은 코드가 쓰여지고 그만큼 복잡해지게 된다.
profile
개발을 꿈꾸는 초짜

0개의 댓글