
코틀린 Command 패턴은 단순히 메서드 호출을 한 번 감싸는 기술이 아닙니다. 실행 요청을 객체로 만들면, 그 요청을 나중에 실행하거나 취소하고, 큐에 담고, 기록으로 남기는 구조가 가능해집니다. 그래서 Command 패턴은 특히 실행과 취소를 함께 다뤄야 하는 문제에서 의미가 커집니다.
이번 글에서는 Command 패턴을 “버튼 하나 누르면 함수 실행” 수준이 아니라, 왜 undo/redo, 실행 기록, 큐잉 구조에 잘 맞는지까지 코틀린 예제로 자세히 설명하겠습니다.

왜 이 패턴이 필요할까
처음에는 단순 함수 호출만으로도 충분해 보입니다. 하지만 기능이 늘어나면 요청을 즉시 실행하는 것만으로는 부족해집니다. 예를 들어 실행 취소가 필요하고, 나중에 다시 실행해야 하고, 사용자의 행동을 히스토리로 보관해야 할 수도 있습니다.
이때 메서드 호출만 흩어져 있으면 누가 무엇을 실행했고 어떻게 취소할지를 추적하기 어려워집니다. Command 패턴은 이 실행 단위를 하나의 객체로 묶어서 구조를 정리합니다.
가장 짧은 정의: 요청을 객체로 만든다
Command 패턴의 핵심은 요청 자체를 독립 객체로 만드는 것입니다. 그 객체 안에는 실행에 필요한 receiver와 파라미터, 그리고 경우에 따라 undo를 위한 정보까지 담길 수 있습니다.
즉 단순히 “무엇을 할까”를 함수 이름으로만 표현하지 않고, 실행 가능한 작업 단위로 다루게 되는 셈입니다.

코틀린 코드로 보면 어떻게 생길까
interface Command {
fun execute()
fun undo()
}
class TextEditor {
private val buffer = StringBuilder()
fun append(text: String) {
buffer.append(text)
}
fun deleteLast(length: Int) {
buffer.delete(buffer.length - length, buffer.length)
}
fun content(): String = buffer.toString()
}
class AppendTextCommand(
private val editor: TextEditor,
private val text: String,
) : Command {
override fun execute() {
editor.append(text)
}
override fun undo() {
editor.deleteLast(text.length)
}
}이 구조에서 중요한 것은 TextEditor가 직접 히스토리나 버튼 이벤트를 몰라도 된다는 점입니다. 실제 실행 단위는 Command 객체가 들고 있고, invoker는 execute나 undo만 호출하면 됩니다.
undo/redo와 왜 잘 맞을까
Command 패턴은 실행을 객체로 만들기 때문에, 이미 실행한 작업을 스택에 쌓아두고 다시 꺼내기가 좋습니다. 그래서 에디터, 드로잉 앱, 작업 히스토리 UI처럼 undo/redo가 필요한 곳에서 자주 떠오릅니다.
- execute()로 작업을 수행한다
- 실행한 Command를 history에 저장한다
- undo()로 마지막 작업을 되돌린다
- 필요하면 redo 스택을 별도로 둔다
즉 Command 패턴은 단순 실행보다, 실행 이후를 구조적으로 다루는 데 더 강합니다.
큐잉과 로그에도 잘 맞는다
요청을 객체로 만들면 당장 실행하지 않고 큐에 넣어둘 수도 있습니다. 그래서 비동기 작업 예약, 배치 처리, 작업 로그 저장과도 잘 연결됩니다.
예를 들어 버튼 클릭이 아니라 “나중에 실행할 작업”이라는 관점으로 보면, Command는 이벤트와 실제 처리 로직을 느슨하게 분리하는 데도 도움이 됩니다.
하지만 언제 과할까
간단한 함수 호출 한두 개만 있는 구조라면 굳이 모든 요청을 Command 객체로 만들 필요는 없습니다. 오히려 클래스 수만 늘어나고 구조가 무거워질 수 있습니다.
- 취소 기능이 전혀 필요 없다
- 실행 이력도 남길 필요가 없다
- 요청을 나중에 다시 실행할 일도 없다
- 단순 함수 호출만으로 충분하다
이런 경우에는 고차 함수나 단순 콜백으로도 충분할 수 있습니다. 즉 Command 패턴은 요청을 객체로 만드는 비용보다 얻는 구조적 이점이 클 때 쓰는 편이 맞습니다.
코틀린에서는 어떻게 더 단순해질까
코틀린에서는 작은 작업이라면 인터페이스 대신 함수 타입으로도 비슷한 감각을 낼 수 있습니다. 하지만 undo처럼 실행의 반대 동작까지 함께 다루려면, 결국 객체로 명시적으로 표현하는 편이 더 선명한 경우가 많습니다.
즉 코틀린은 Command 패턴을 완전히 없애기보다, 작은 경우에는 가볍게 줄이고 복잡한 경우에는 더 또렷하게 드러내게 도와준다고 보는 편이 맞습니다.
비슷한 패턴과 무엇이 다를까
Strategy는 어떤 알고리즘을 선택할지에 더 가깝고, Chain of Responsibility는 여러 처리기가 순서대로 판단하게 만드는 흐름에 더 가깝습니다. Command는 그 중에서도 “실행 요청 자체를 객체화한다”는 점이 핵심입니다.
즉 Strategy는 선택, Chain은 흐름, Command는 실행 단위라는 차이로 보면 덜 헷갈립니다.
마무리
코틀린 디자인 패턴 14편의 핵심은 이렇습니다. Command 패턴은 함수를 한 번 감싸는 장난감 구조가 아니라, 요청을 객체로 만들어 실행, 취소, 재실행, 기록을 같은 단위로 다루게 해주는 패턴입니다.
그래서 이 패턴은 호출 자체보다 호출 이후를 구조적으로 관리해야 하는 상황에서 특히 빛납니다. undo/redo, 작업 큐, 실행 로그가 필요하면 Command 패턴을 떠올릴 가치가 큽니다.
코틀린 디자인 패턴 시리즈
코틀린 디자인 패턴 시리즈(0) – 왜 아직도 디자인 패턴을 배워야 할까
코틀린 디자인 패턴(1) – Singleton 패턴은 언제 쓰고 object는 어떻게 다를까
코틀린 디자인 패턴(2) – Factory Method 패턴으로 생성 책임 나누기
코틀린 디자인 패턴(3) – Abstract Factory 패턴은 언제 필요할까
코틀린 디자인 패턴(4) – Builder 패턴과 named argument는 어떻게 다를까
코틀린 디자인 패턴(5) – Prototype 패턴과 data class copy 정리
코틀린 디자인 패턴(6) – Adapter 패턴으로 기존 코드를 새 인터페이스에 맞추기
코틀린 디자인 패턴(7) – Bridge 패턴은 상속 폭발을 어떻게 줄일까
코틀린 디자인 패턴(8) – Composite 패턴으로 트리 구조 다루기
코틀린 디자인 패턴(9) – Decorator 패턴은 상속 대신 어떻게 확장할까
코틀린 디자인 패턴(10) – Facade 패턴으로 복잡한 시스템 감추기
코틀린 디자인 패턴(11) – Flyweight 패턴은 메모리를 어떻게 아낄까