본문 바로가기

IT/헤드퍼스트 디자인패턴

[디자인패턴] 커맨드패턴

안녕하세요 남갯입니다

오늘은 커맨드 패턴에 대해 포스팅해보려고합니다


사건의 발단

이전의 날씨 관련 옵져버 패턴 프로그래밍을 성공적으로 완수하면서 추가적인 일을 맡게되었습니다.

각 프로그래밍이 가능한 슬롯이 있는 홈 오토메이션 리모컨의 API의 디자인을 의뢰받았습다.


실제 가전제품에는 TV , Light garage 등 각자의 다른 클래스들의 동작이 있었습니다.


TV = on, off, setchannel , setvolume

light = on, off

garage = up, down , stop, lighton, lightdown, lock

........


클래스를 보아하니 공통적인것은 없을것이고 앞으로 이런 클래스가 더 추가가 가능할지도 모릅니다.


그래서 우리는 커맨드패턴을 이용하기로 했습니다.


실제 식당에서 주문을 받는것처럼

고객 -> createOrder -> 계산서 -> 웨이터 -> orderup -> 지시서 -> makeHamburger -> 주방장 -> 결과


이런형태로 진행을 하게됩니다.


실제 이걸 커맨드패턴으로 적용할 수 있습니다.


interface Command{
fun execute()
}


커맨드 인터페이스를 만들고 실제 실행할 함수를 만듭니다.


class Light {
fun on() = println("light on")
fun off() = println("light off")
}

class Garage() {
fun up() = println("garage up")
fun down() = println("garage down")
fun stop() = println("garage stop")
}

class LightOnCommand(val light: Light) : Command {
override fun execute() {
light.on()
}
}

class GarageOnCommand(val garage: Garage) : Command {
override fun execute() {
garage.up()
}
}



그리고 각 동작의 클래스와 그 동작의 커맨드를 만듭니다.


class SimpleRemoteControl {
private lateinit var slot: Command
fun setCommand(command: Command) {
slot = command
}
fun buttonPressed() = slot.execute()
}


그리고 그 각 동작을 제어하는 리모컨을 만듭니다.


fun main() {
val remote = SimpleRemoteControl()
val light = Light()
val garage = Garage()
val lightCommand = LightOnCommand(light)
val garageCommand = GarageOnCommand(garage)
remote.setCommand(lightCommand)
remote.buttonPressed()
remote.setCommand(garageCommand)
remote.buttonPressed()
}


이를 통해 커맨드패턴의 정의를 살펴보게되면



커맨드패턴의 정의


커맨드패턴을 이용하게되면 요구사항을 객체로 캡슐화가 가능하며 매개변수를 써서 다른 요구사항을 넣을 수 있습니다.

또한 요청내역을 큐에 저장하거나 로그로 기록이 가능합니다.


실제 위의 예제코드에서는 일련의 동작들을 리시버라는 동작에 집어넣고 excute라는 메소르만을 공유함으로서 동작을 처리합니다

실제 외부에서는 어떤일을 하는지 어떤동작을 하는지는 외부에서는 알 수 없습니다.



*궁금증+

부엌과 거실의 light를 나눌때는 어떻게해야할까?

lightCommand 를 두개만들어 거실용과 부엌용으로 만들어서 처리하면 됩니다.




메타 커맨드 패턴

커맨드패턴을 확장하게되면 큐나 로그를 구현하거나 작업을 취소하는 방법을 배울 수 있습니다. 그것이 커맨드패턴의 확장인 메타커맨드 패턴입니다.

메타 커맨드 패턴을 이용하게되면 명령으로 이루어진 메크로를 만들어서 여러 명령을 실행 가능합니다.



이제 아까의 요구사항인 7개의 슬롯에 각가의 동작을 넣는 코드를 만들어봅시다


class RemoteControl {
var onCommands = arrayOfNulls<Command>(7)
var offCommands = arrayOfNulls<Command>(7)

init {
val noCommand = NoCommand()
for (i in 0 until 7) {
onCommands[i] = noCommand
offCommands[i] = noCommand
}
}

fun setCommand(slot: Int, onCommand: Command, offCommand: Command) {
for (i in 0 until 7) {
onCommands[i] = onCommand
offCommands[i] = offCommand
}
}
fun onButtonPressed(slot: Int){
onCommands[slot]?.execute()
}
fun offButtonPressed(slot: Int){
offCommands[slot]?.execute()
}

}


7개의 동작을 리모컨으로 제어하는 RemoteControl이라는 클래스를 만들어놓고


아까와는 다르게 LightOnCommand과 동일한 Off용 커맨드를 만듭니다.


class LightOffCommand(val light: Light) : Command {
override fun execute() {
light.off()
}
}


class GarageOffCommand(val garage: Garage) : Command {
override fun execute() {
garage.down()
}
}



실제의 동작과정은 위의 simpleRemote와 동일하게


원하는 해당 슬롯에 on용 off용 light, garage를 넣고 onbutton(슬롯)을 동작시키면 완성됩니다.



마지막으로 요청사항이였던 undo버튼은 어떻게 만들까요?



interface Command {
fun execute()
fun undo()
}

되게 간단하죠???!!


이렇게 만들고 실제 구현코드에 반대되는 행동을 구현하게 되면 취소가 가능합니다.


class LightOnCommand(val light: Light) : Command {
override fun execute() {
light.on()
}

override fun undo() {
light.off()
}
}


아까의 코드에 undo함수에 반대되는 행동을 각각 추가해준뒤에


fun onButtonPressed(slot: Int){
onCommands[slot]?.execute()
undoCommand = onCommands[slot]
}

실제 동작부분에 마지막 동작을 넣어줌으로서


undoButtonPressed라는 함수를 이용해 마지막 동작을 undo시키는 거죠!


실제 메크로 커맨드를 통해 모든 동작을 한꺼번에 커맨드가 가능합니다.



class MacroCommand(val commands : Array<Command>) : Command{
override fun execute() {
for (command in commands)
command.execute()
}
}



------숙제에서는 undo를 넣을 예정 , history 쌓는것도 넣을 예정------