(인프런) 코딩으로 학습하는 GoF의 디자인 패턴 - 백기선, 강의를 보고 정리한 글입니다.
코드는 GitHub 에 있습니다
#1. 객체 생성 관련 패턴 |
#2. 구조 관련 패턴 |
#3. 행동 관련 패턴 |
||
✔️ 커맨드 패턴이란 (Command Patterns)
요청을 캡슐화 하여 호출자(Invoker) 와 수신자(receiver)를 분리하는 패턴
- 커맨드 패턴은, 객체의 행위(메서드)를 클래스로 만들어 캡슐화하여 호출자와 수신자의 결합도(의존성)를 분리하는 패턴입니다.
- 호출자(invoke) 객체가 어떠한 행위를 가지는 응답자(receiver) 객체의 기능을 직접적으로 호출한다면, 호출자 객체의 응답자의 메소드를 호출하는 부분의 코드가 수정되거나 || 응답자 객체의 코드가 수정되면 한쪽이 수정되더라도 양쪽 모두 수정이 되는 강한 결합상태를 가집니다.
- 커맨드 패턴은 이러한 2 객체간의 의존성을 제거하기위한 패턴입니다.
✔️ 커맨드 패턴 사용 예시
먼저 커맨드 패턴을 적용하기 전에 예시를 들어보겠습니다
특정한 버튼을 누르면 "불이 켜진다" 라는 기능을 가지는 객체가 있다고 생각해봅시다.
- Button 이라는 객체가 Invoker(호출자)가 되어 Light 객체 (Receiver) 의 "불을 켜다" 라는 기능을 수행하는 메소드를 호출하고 있습니다.
- → 여기서 버튼을 눌렀을 때 "불을 끄고싶다." 로 요구사항이 변경되면 어떻게 될까요?
//호출자 (Invoker)
@AllArgsConstructor
public class Button {
private Light lights;
public void press() {
lights.on();
}
public static void main(String[] args) {
Button button = new Button(new Light());
button.press();
button.press();
button.press();
}
}
//응답자 (Receiver)
public class Light {
private boolean isOn;
public void on() {
System.out.println("불을 켭니다.");
this.isOn = true;
}
}
- 먼저 Light 객체의 "불을 크다" 라는 기능을 수행하는 메소드가 추가되고, Button 객체의 receiver 를 호출하는 부분이 수정되야겠죠
- 즉, 지금의 구조는 2 객체 모두가 수정되어야하는 강한 결합상태 입니다.
@AllArgsConstructor
public class Button {
private Light lights;
public void press() {
//요청사항이 바뀌면 코드가 수정되어야함
lights.off()
}
//생략
}
public class Light {
private boolean isOn;
public void on() {
System.out.println("불을 켭니다.");
this.isOn = true;
}
public void off() {
System.out.println("불을 끕니다.");
this.isOn = false;
}
}
👏🏻 이 두 객체, 즉 호출자와 응답자 객체의 의존성을 제거하기위해 "커맨드 패턴" 을 사용합니다.
- 지금은 버튼을 누를 때 "불을 키다", "불을 끄다" 이지만 → 버튼을 누를 때 게임을 시작한다 | 불꽃놀이가 터진다 |
주식이 폭등한다... 등등 다양한 요구사항이 생기고, 그럴 때 마다 중복에 가까운 객체가 마구마구 생성된거죠 - 이런 커플링(강한 결합도)를 해결하기 위해 버튼을 누를때의 실행되는 행위를 캡슐화 합니다.
Command 객체
- 버튼을 누를때 어떤 행위를 할지는 이 Command 인터페이스를 통해 호출합니다.
- 이렇게 행위를 추상화하고, 내부 기능을 캡슐화함으로써 호출자(Invoker) 는 응답자(Receiver) 의 구체적인 사항을 몰라도 됩니다. (의존성 분리)
// JAVA Runnable 유사
public interface Command {
void execute();
}
CommandImpl 객체
- 그럼 이 기능을 구현하는 캡슐화 객체들이 존재하겠죠 간단하게 "불을 킨다" , "불을 끈다 이 2가지로 기능을 구현해 보겠습니다.
- 여기서 주의해야할 점은 CommandImpl 객체가 어떤 작업을 수행하기 위해서는 필요한 모든 요소들을 알고 있어야 합니다.
//커멘드에는 어떤 작업을 하기위해 필요한 모든 요소들이 들어와야한다.
@AllArgsConstructor
public class LightOnCommand implements Command{
private Light light;
@Override
public void execute() {
light.on();
}
}
@AllArgsConstructor
public class LightOffCommand implements Command{
private Light light;
@Override
public void execute() {
light.off();
}
}
Button 호출자 객체 (Invoker)
- 호출자는 Command 의 "실행" 메소드만을 바라보고 구체적인 기능은 내부로 캡슐화 되어있습니다.
- 그렇기 때문에, 기능이 변경되더라도 응답자의 구현체가 추가되거나, 수정되어집니다.
- 호출자(Button) 객체의 command 를 수행하는 부분은 수정이 일어나지 않는 구조가 되어집니다.
public class Button {
private final Command command;
public Button(Command command) {
this.command = command;
}
public void press() {
command.execute();
}
public static void main(String[] args) {
/**
* Command 가 바뀌거나, 요구사항이 추가되더라도 invoker 는 수정이 일어나지 않아도 된다.
* 수정의 범위가 축소, Command 재사용성의 증가, 책임의 분리
*/
Button button_1 = new Button(new LightOffCommand(new Light()));
Button button_2 = new Button(new LightOffCommand(new Light()));
button_1.press();
button_2.press();
}
}
+ 게임을 시작하다! 라는 기능을 추가해도 기능을 정의한 구현체만 추가되지 Button의 코드는 수정되지 않습니다.
@AllArgsConstructor
public class GameStartCommand implements Command{
private Game game;
@Override
public void execute() {
game.start();
}
}
public class Button {
private final Command command;
public Button(Command command) {
this.command = command;
}
public void press() {
command.execute();
}
public static void main(String[] args) {
/**
* Command 가 바뀌거나, 요구사항이 추가되더라도 invoker 는 수정이 일어나지 않아도 된다.
* 수정의 범위가 축소, Command 재사용성의 증가, 책임의 분리
*/
Button button_1 = new Button(new LightOffCommand(new Light()));
Button button_2 = new Button(new LightOffCommand(new Light()));
Button button_3 = new Button(new GameStartCommand(new Game()));
button_1.press();
button_2.press();
button_3.press();
}
}
✔️ 커맨드 패턴 장,단점
장점
- 기존 코드를 변경하지 않고 새로운 커맨드를 만들 수 있다.
- 수신자의 코드가 변경되어도 호출자의 코드는 변경되지 않는다. → 수정의 범위가 축소된다.
- 커맨드 객체를 로깅, DB에 저장, 네트워크로 전송 하는 등 다양한 방법으로 활용할 수도 있다.
단점
- 클래스가 많아진다.
이렇게 기능을 캡슐화하여 호출하는 쪽과 기능을 제공하는 쪽의 결합도를 분리하고 싶을 때
커맨드 패턴을 사용하면 두 객체간의 의존성을 제거하는데 좋을거같다는 생각이 들었습니다.. 끝!
'Java > Design Pattern' 카테고리의 다른 글
[디자인 패턴] 행동 패턴 - 이터레이터 패턴 (반복자 패턴 - Iterator Pattern) (0) | 2023.04.04 |
---|---|
[디자인 패턴] 행동 패턴 - 인터프리터 패턴 (Interpreter Pattern) (0) | 2023.03.31 |
[디자인 패턴] 구조 패턴 - 프록시 패턴 (Proxy Pattern) (0) | 2023.03.24 |
[디자인 패턴] 구조 패턴 - 플라이웨이트 패턴 (Flyweight Pattern) (0) | 2023.03.15 |
[디자인 패턴] 구조 패턴 - 퍼사드 패턴 (Facade Pattern) (0) | 2023.03.09 |