커맨드(Command) 패턴
요청을 객체로 캡슐화하여 호출자(invoker)와 수신자(receiver)를 분리하는 패턴
요청을 처리하는 방법이 바뀌더라도, 호출자의 코드는 변경되지 않는다.
Command 인터페이스
실행할 명령을 추상적으로 정의한다.
ConcreteCommand 클래스
실제 명령을 구현한 클래스 Command 인터페이스를 구현한다.
수행할 구체적인 행동과 수신자(receiver)를 포함한다.
Receiver (수신자)
실제 명령의 실행 방법을 알고 있는 객체로, ConcreteCommand 클래스에서 수신자 객체를 통해 요청을 수행
Invoker (호출자)
명령 객체(Command)를 받아 execute()를 호출하는 역할을 한다.
호출자는 명령의 구체적인 실행 방법에 대해 알 필요 없이, 명령 객체를 호출하여 요청을 전달할 수 있다.
Client(클라이언트)
구체적인 명령 객체를 생성하여 호출자에게 전달하는 역할을 수행
커맨드(Command) 패턴 before
구글홈이라고 "OK Google 히터 틀어줘"라고 하면, 히터를 틀어주는 실제 구글 서비스가 있다.
구글홈을 사용하는 사용자를 Client 클래스
구글홈을 OKGoogle 클래스
히터를 Heater 클래스로 정의하겠다.
Heater
public class Heater {
public void powerOn(){
System.out.println("Heater On");
}
public void powerOff(){
System.out.println("Heater Off");
}
}
OKGoogle
public class OKGoogle {
public static enum CONTROL{
ON, OFF
}
private Heater heater;
public OKGoogle(Heater heater){
this.heater = heater;
}
public void call(CONTROL control){
if(control.equals(CONTROL.ON)){
heater.powerOn();
}
else heater.powerOff();
}
}
Client
public class Client {
public static void main(String[] args) {
Heater heater = new Heater();
OKGoogle okGoogle = new OKGoogle(heater);
okGoogle.call(OKGoogle.CONTROL.ON);
okGoogle.call(OKGoogle.CONTROL.OFF);
}
}
// 출력
Heater On
Heater Off
OKGoogle에서 히터를 기능 말고, 불을 켜는 기능을 추가하고 싶다면 어떻게 해야 할까?
Lamp 클래스를 정의하고, OKGoogle 클래스에서 Lamp 객체를 참조하도록 해야 한다.
Lamp 클래스
public class Lamp {
public void turnOn(){
System.out.println("Lamp On");
}
public void turnOff(){
System.out.println("Lamp Off");
}
}
OKGoogle 클래스
public class OKGoogle {
public static enum MODE {
HEATER, LAMP
}
public static enum CONTROL{
ON, OFF
}
private MODE mode;
private Heater heater;
private Lamp lamp;
public OKGoogle(Heater heater, Lamp lamp){
this.heater = heater;
this.lamp = lamp;
}
public void setMode(MODE mode){
this.mode = mode;
}
public void call(CONTROL control){
if(Objects.isNull(mode)){
throw new IllegalStateException("mode is not defined");
}
switch (mode){
case LAMP -> {
if(CONTROL.ON.equals(control)){
lamp.turnOn();
}
else lamp.turnOff();
}
case HEATER -> {
if(CONTROL.ON.equals(control)){
heater.powerOn();
}
else heater.powerOff();
}
}
}
}
Client
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(OKGoogle.MODE.HEATER);
okGoogle.call(OKGoogle.CONTROL.ON);
okGoogle.call(OKGoogle.CONTROL.OFF);
okGoogle.setMode(OKGoogle.MODE.LAMP);
okGoogle.call(OKGoogle.CONTROL.ON);
okGoogle.call(OKGoogle.CONTROL.OFF);
}
}
// 출력
Heater On
Heater Off
Lamp On
Lamp Off
현재 코드의 문제점은 OKGoogle은 Heater, Lamp 객체를 참조로 가진다.
만약 기능이 많아지만 가지는 객체의 참조는 계속 늘어난다.
또한 enum 타입 또한 늘어나고, call() 메소드에서 분기가 계속 늘어난다.
이런 경우 OCP에 위배된다.
커맨드(Command) 패턴 after
Command 인터페이스
public interface Command {
void execute();
void undo();
}
undo() 메소드
제일 최근에 했던 command의 동작과 반대되는 행위를 한다.
ex) heater on → heater off
ConcreteCommand - 4가지 Command 구현체
public class HeaterOnCommand implements Command{
private Heater heater;
public HeaterOnCommand(Heater heater) {
this.heater = heater;
}
@Override
public void execute() {
heater.powerOn();
}
@Override
public void undo() {
new HeaterOffCommand(this.heater).execute();
}
}
public class HeaterOffCommand implements Command{
private Heater heater;
public HeaterOffCommand(Heater heater) {
this.heater = heater;
}
@Override
public void execute() {
heater.powerOff();
}
@Override
public void undo() {
new HeaterOnCommand(this.heater).execute();
}
}
public class LampOnCommand implements Command{
private Lamp lamp;
public LampOnCommand(Lamp lamp) {
this.lamp = lamp;
}
@Override
public void execute() {
lamp.turnOn();
}
@Override
public void undo() {
new LampOffCommand(this.lamp).execute();
}
}
public class LampOffCommand implements Command{
private Lamp lamp;
public LampOffCommand(Lamp lamp) {
this.lamp = lamp;
}
@Override
public void execute() {
lamp.turnOff();
}
@Override
public void undo() {
new LampOnCommand(this.lamp).execute();
}
}
undo() 메소드의 로직에서 왜 참조하고 있는 객체의 메소드를 바로 호출하지 않는가?
내가 생각하는 Reciver의 메소드를 직접 호출하는 게 아닌 반대 성격의 command에게 위임했을 때의 장점은 변경이 최소화된다는 것이었다.
만약 undo에서 직접 호출한다고 하면?
Reciver의 메소드가 변경이 된다고 하면 해당 메소드를 execute()에서 호출하고 있는 command와 undo에서 호출하고 있는 command 2곳에서 변경이 일어난다.
하지만 반대 성격의 command에게 위임한다면?
execute()에서 호출하고 있는 command만 변경하면 된다.
Client
public class Client {
public static void main(String[] args) {
OKGoogle okGoogle = new OKGoogle();
okGoogle.call(new HeaterOnCommand(new Heater()));
okGoogle.call(new LampOnCommand(new Lamp()));
okGoogle.undo();
okGoogle.undo();
}
}
// 출력
Heater On
Lamp On
Lamp Off
Heater Off
이제 OKGoogle(Invoker)은 직접적으로 Lamp / Heater (Receiver)에게 의존하지 않게 되었다.
원하는 동작을 가지는 Command만 생성해서 넣어주면 된다.
또한 이런 Command들은 생성하고 재사용이 가능하다.
+ undo 기능도 사용 가능
커맨드(Command) 패턴 정리
장점
(Invoker)기존 코드를 변경하지 않고 새로운 커맨드를 만들 수 있다
(Receiver)수신자의 코드가 변경되어도 호출자의 코드는 변경되지 않는다
커맨드 객체를 로깅, DB에 저장, 네트워크로 전송 하는 등 다양한 방법으로 활용할 수도 있다.
단점
코드가 복잡하고 클래스가 많아진다.
커맨드(Command) 패턴 적용 사례
자바
public class CommandInJava {
public static void main(String[] args) {
Heater heater = new Heater();
Lamp lamp = new Lamp();
ExecutorService executorService = Executors.newFixedThreadPool(4);
executorService.submit(heater::powerOn);
executorService.submit(heater::powerOff);
executorService.submit(lamp::turnOn);
executorService.submit(lamp::turnOff);
}
}
Runnable / 람다 / 메소드 래퍼런스 → 커맨드 패턴
스프링
public class CommandInSpring {
private DataSource dataSource;
public CommandInSpring(DataSource dataSource) {
this.dataSource = dataSource;
}
public void add(Command command){
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource)
.withTableName("command")
.usingGeneratedKeyColumns("id");
Map<String, Object> data = new HashMap<>();
data.put("name", command.getClass().getSimpleName());
data.put("when", LocalDateTime.now());
insert.execute(data);
}
}
SimpleJdbcInsert: ConcreteCommand
참고자료
'Computer Sience > Desgin Pattern' 카테고리의 다른 글
[Design Pattern] 이터레이터(Iterator) 패턴 (2) | 2024.10.31 |
---|---|
[Design Pattern] 인터프리터(Interpreter) 패턴 (0) | 2024.10.31 |
[Design Pattern] 책임 연쇄(Chain of Responsibility) 패턴 (1) | 2024.10.31 |
[Design Pattern] 프록시(Proxy) 패턴 + 다이나믹 프록시 (0) | 2024.10.30 |
[Design Pattern] 플라이웨이트(Flyweight) 패턴 (1) | 2024.10.30 |