안녕하세요 남갯입니다
오늘은 커맨드 패턴에 대해 포스팅해보려고합니다
사건의 발단
이전의 날씨 관련 옵져버 패턴 프로그래밍을 성공적으로 완수하면서 추가적인 일을 맡게되었습니다.
각 프로그래밍이 가능한 슬롯이 있는 홈 오토메이션 리모컨의 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 쌓는것도 넣을 예정------
'책 > 헤드퍼스트 디자인패턴' 카테고리의 다른 글
[디자인패턴] 템플릿 메소드 패턴 (0) | 2019.06.05 |
---|---|
[디자인패턴] 어댑터 패턴과 퍼사드 패턴 (2) | 2019.05.21 |
[디자인패턴] 팩토리패턴 (4) | 2019.05.09 |
[디자인패턴] 데코레이터 패턴 (4) | 2019.05.07 |
[디자인패턴] 옵저버패턴 (0) | 2019.04.29 |