본문 바로가기
DesignPattern

[Design Pattern] Command Pattern

by 봄석 2019. 10. 21.

[Design Pattern] Command Pattern

Command Pattern에 대하여 알아보고 , 자바 코틀린 예제를 보고 확인해보도록 하겠습니다.

 

 

 

Command Pattern이란??

실행될 기능을 캡슐화함으로써 주어진 여러 기능을 실행할 수 있는 재사용성이 높은 클래스를 설계하는 패턴
즉, 이벤트가 발생했을 때 실행될 기능이 다양하면서도 변경이 필요한 경우에 이벤트를 발생시키는 클래스를 변경하지 않고 재사용하고자 할 때 유용합니다.

 

 

실행될 기능을 캡슐화함으로써 기능의 실행을 요구하는 호출자(Invoker) 클래스와

실제 기능을 실행하는 수신자(Receiver) 클래스 사이의 의존성을 제거합니다.


따라서 실행될 기능의 변경에도 호출자 클래스를 수정 없이 그대로 사용할 수 있도록 줍니다.

 

클래스가 수행하는 작업

  • Command
    • 실행될 기능에 대한 인터페이스
    • 실행될 기능을 execute 메서드로 선언함
  • ConcreteCommand
    • 실제로 실행되는 기능을 구현
    • 즉, Command라는 인터페이스를 구현함
  • Invoker
    • 기능의 실행을 요청하는 호출자 클래스
  • Receiver
    • ConcreteCommand에서 execute 메서드를 구현할 때 필요한 클래스
    • 즉, ConcreteCommand의 기능을 실행하기 위해 사용하는 수신자 클래스

 

Command Pattern with Java

public interface Command { public abstract void execute(); }

 

invoker

public class Button {
    private Command theCommand;

    public Button(Command theCommand) {
        setCommand(theCommand);
    }

    public void setCommand(Command newCommand) {
        this.theCommand = newCommand;
    }

    public void pressed() {
        theCommand.execute();
    }
}

 

Receiver

public class Alarm {
    public void start() {
        System.out.println("Alarming");
    }
}
public class Lamp {
    public void turnOn() {
        System.out.println("Lamp On");
    }
}

 

ConcreteCommand

public class AlarmStartCommand implements Command {
    private Alarm theAlarm;

    public AlarmStartCommand(Alarm theAlarm) {
        this.theAlarm = theAlarm;
    }

    public void execute() {
        theAlarm.start();
    }
}
public class LampOnCommand implements Command {
    private Lamp theLamp;

    public LampOnCommand(Lamp theLamp) {
        this.theLamp = theLamp;
    }

    public void execute() {
        theLamp.turnOn();
    }
}

 

 

Command Pattern with Kotlin

ex1) Order  Dish

 

command -> typealias Order

ConcreteCommand -> cookStarter, cookMainCourse, cookDessert

typealias Order = () -> String

fun cookStarter(name: String): Order = { "$name Starter" }

fun cookMainCourse(name: String): Order = { "$name Main Course" }

fun cookDessert(name: String): Order = { "$name Dessert" }

Invoker

class Waiter(private val pendingOrders: MutableList<Order> = mutableListOf()) {
    fun acceptOrder(vararg orders: Order) = pendingOrders.addAll(orders)

    fun serveOrders(): List<String> = pendingOrders.map { it() }
}

 

Receiver -> Client(고객)

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class CommandTest {

    @Test
    @DisplayName("Given a set of dishes and a waiter. When order dishes. Then serve ordered dishes")
    fun givenSetOfDishesAndWaiter_whenOrderDishes_thenServeOrderedDishes() {
        // Given
        val starter: Order =
            cookStarter("Salad")
        val mainCourse: Order =
            cookMainCourse("Chicken")
        val dessert: Order =
            cookDessert("Fruit")
        val waiter = Waiter()

        // When
        waiter.acceptOrder(starter, mainCourse, dessert)
        val dishes = waiter.serveOrders()

        // Then
        val expectedDishes = listOf("Salad Starter", "Chicken Main Course", "Fruit Dessert")
        assertThat(dishes, CoreMatchers.equalTo(expectedDishes))
    }
}

 

 ex2) Order and Pay

 

command

interface OrderCommand {
    fun execute()
}

 

ConcreteCommand

class OrderAddCommand(private val id: Long) : OrderCommand {
    override fun execute() = println("Adding order with id: $id")
}
class OrderPayCommand(private val id: Long) : OrderCommand {
    override fun execute() = println("Paying for order with id: $id")
}

 

invoker

class CommandProcessor {

    private val queue = ArrayList<OrderCommand>()

    fun addToQueue(orderCommand: OrderCommand): CommandProcessor =
        apply { queue.add(orderCommand) }

    fun processCommands(): CommandProcessor =
        apply {
            queue.forEach { it.execute() }
            queue.clear()
        }
}

 

Receiver(Client, 고객)

class CommandTest {

    @Test
    fun Command() {
        CommandProcessor()
            .addToQueue(OrderAddCommand(1L))
            .addToQueue(OrderAddCommand(2L))
            .addToQueue(OrderPayCommand(2L))
            .addToQueue(OrderPayCommand(1L))
            .processCommands()
    }
}

리시버에게 통보

 

 

 

 

정리

실행될 기능을 캡슐화하고 , 실행될 기능을 커맨드를 통해  실행하는 패턴입니다.

기능이 실행된 후에는  Receiver에 수행된 기능에 대하여 알려줍니다.

 

Command Pattern으로 구현하게 될 경우 확장에는 열려있고, 변경에는 닫혀있는 

즉 기존의 코드를  수정하지 않고 확장할 수 있는 여지가 많아지게 됩니다( OCP를 지킬 수 있습니다.)

 

 

 

샘플 소스  보러 가기

https://github.com/qjatjr1108/DesignPattern

 

qjatjr1108/DesignPattern

DesignPattern Sample. Contribute to qjatjr1108/DesignPattern development by creating an account on GitHub.

github.com

 

댓글