개발
명령을 객체로! 커맨드 패턴으로 유연한 실행 로직 만들기 🚀
스택큐힙리스트
2025. 7. 23. 23:10
반응형
왜 또 커맨드 패턴인가?
버튼 하나에 기능을 자꾸 갈아끼워야 할 때마다 if-else 덩어리를 늘리는 대신, “명령 자체를 객체로 포장”하면 된다. 요청(함수 이름·인자·수행 시점)을 통째로 캡슐화해 큐에 쌓거나, 로그로 남기거나, 실행 ↔ 취소까지 자유자재로 다룰 수 있다는 얘기다.
패턴 한 줄 정의
Invoker(요청자)가 Command(명령 객체)를 실행하면, 명령은 Receiver(실제 작업자)에게 일을 시킨다.
Invoker는 “누가 어떻게 일하느냐”에 전혀 관여하지 않는다. 때문에 실행 시점 지연, 일괄 처리, undo/redo, 트랜잭션 롤백 같은 고급 기능을 끼워 넣기 쉽다.
핵심 구성 요소
- Command 인터페이스: execute()·undo() 같은 동작 규약
- ConcreteCommand: 실제 로직과 Receiver 참조 보관
- Receiver: 명령을 수행할 도메인 객체
- Invoker: 명령을 받아 실행·취소·재실행 등 타이밍 제어
- Client: 누가 어떤 ConcreteCommand를 Invoker에 심을지 결정
코틀린 예제 – 리모컨 & 가전
// Command
fun interface Command { fun execute() }
// Receiver
class Light {
fun on() = println("💡 불 켜짐")
fun off() = println("💡 불 꺼짐")
}
// ConcreteCommand
class LightOnCommand(private val light: Light) : Command {
override fun execute() = light.on()
}
// Invoker
class RemoteControl {
private val slots = mutableMapOf<Int, Command>()
fun setCommand(slot: Int, cmd: Command) { slots[slot] = cmd }
fun press(slot: Int) = slots[slot]?.execute()
}
// 사용
fun main() {
val light = Light()
val remote = RemoteControl()
remote.setCommand(0, LightOnCommand(light))
remote.press(0) // 💡 불 켜짐
}
명령이 객체이므로 remote.setCommand()만으로 버튼 역할을 핫스왑할 수 있다. undo()를 추가해 실행 내역 스택을 관리하면 스마트홈 앱에서도 그대로 쓸 수 있다.
실전에서 빛나는 순간
- Undo / Redo: 포토샵, IDE 단축키
- 매크로·배치 작업: 여러 명령을 List로 묶어 한 번에 실행
- CQRS/이벤트 소싱: “쓰기 명령(Command) vs 읽기(Query)” 분리 구조의 근간
- 스프링 부트: ApplicationRunner, CommandLineRunner가 대표적인 Command 구현체다.
장점 🔥
- 요청, 실행 시점을 분리해 스케줄링·트랜잭션 재시도가 간단
- OCP 준수: 새 명령 클래스만 만들면 Invoker 수정 없음
- 로깅·모니터링이 쉬워 운영 관점에서도 유리
주의할 점 ⚠️
- 명령 종류가 폭증하면 클래스 수도 급격히 늘어난다.
- 간단한 호출이면 람다 전달이 더 짧고 명료할 때도 있다.
반응형