1️⃣ Command 패턴이란?
Command 패턴은 요청(명령)을 객체로 캡슐화하여 실행, 취소, 저장 등을 관리할 수 있도록 하는 디자인 패턴
즉, "어떤 작업을 실행하는 코드"를 하나의 객체로 만들고, 이를 실행/취소/보관할 수 있도록 하는 패턴
📍 Command 패턴의 핵심 개념
- 명령을 객체(Command)로 만들어 독립적으로 다룸
- 명령을 실행하는 대상(Receiver)과 실행을 요청하는 객체(Invoker)를 분리
- 명령을 저장하고 나중에 실행 가능
- Undo(실행 취소) 기능을 쉽게 구현
2️⃣ Command 패턴의 구조
Command 패턴은 다음과 같은 5가지 주요 컴포넌트로 구성
🔹 1. Command (명령 인터페이스)
- 실행할 작업을 정의하는 인터페이스
- 모든 명령은 execute() 메서드를 구현
- 실행 취소(Undo)가 필요하다면 undo() 메서드도 추가 가능
🔹 2. ConcreteCommand (구체적인 명령)
- Command 인터페이스를 구현하여 실제 동작을 수행하는 클래스를 만듬
- 실행할 작업을 Receiver 객체에 전달
🔹 3. Receiver (작업을 실제 수행하는 객체)
- 명령을 수행하는 실제 객체
- Command 객체는 Receiver를 호출하여 작업을 실행
🔹 4. Invoker (명령을 실행하는 객체)
- Command 객체를 실행하는 역할
- 사용자는 Invoker를 통해 Command를 실행하거나 취소 가능
🔹 5. Client (클라이언트)
- Command, Invoker, Receiver를 생성하고 연결하는 역할
3️⃣ Command 패턴 예제
💡 전등 켜고 끄기
- 스마트 홈에서 전등을 켜고 끄는 기능을 만들고 싶다.
- 버튼을 누르면 전등이 켜지고(on), 다시 누르면 꺼진다(off).
- 명령을 객체로 만들고 나중에 실행/취소할 수도 있어야 한다.
📌 Command 패턴으로 구현하기
/** 1️⃣ Command 인터페이스 (모든 명령의 공통 인터페이스) */
interface Command {
fun execute() // 명령 실행
fun undo() // 실행 취소 (Undo)
}
/** 2️⃣ Receiver (실제 동작을 수행하는 객체) */
class Light {
fun turnOn() {
println("💡 전등이 켜졌습니다!")
}
fun turnOff() {
println("🔦 전등이 꺼졌습니다!")
}
}
/** 3️⃣ ConcreteCommand (전등 켜기 명령) */
class LightOnCommand(private val light: Light) : Command {
override fun execute() {
light.turnOn()
}
override fun undo() {
light.turnOff()
}
}
/** 4️⃣ ConcreteCommand (전등 끄기 명령) */
class LightOffCommand(private val light: Light) : Command {
override fun execute() {
light.turnOff()
}
override fun undo() {
light.turnOn()
}
}
/** 5️⃣ Invoker (사용자가 버튼을 누르면 명령을 실행) */
class RemoteControl {
private var command: Command? = null
fun setCommand(command: Command) {
this.command = command
}
fun pressButton() {
command?.execute()
}
fun pressUndo() {
command?.undo()
}
}
/** 6️⃣ 클라이언트 코드 */
fun main() {
val light = Light() // 전등 객체 생성
val lightOnCommand = LightOnCommand(light) // 전등 켜기 명령 생성
val lightOffCommand = LightOffCommand(light) // 전등 끄기 명령 생성
val remote = RemoteControl() // 리모컨(Invoker) 생성
// 전등 켜기 버튼 설정 및 실행
remote.setCommand(lightOnCommand)
remote.pressButton() // 💡 전등이 켜졌습니다!
// 실행 취소
remote.pressUndo() // 🔦 전등이 꺼졌습니다!
// 전등 끄기 버튼 설정 및 실행
remote.setCommand(lightOffCommand)
remote.pressButton() // 🔦 전등이 꺼졌습니다!
// 실행 취소
remote.pressUndo() // 💡 전등이 켜졌습니다!
}
🔹 실행 결과
💡 전등이 켜졌습니다!
🔦 전등이 꺼졌습니다!
🔦 전등이 꺼졌습니다!
💡 전등이 켜졌습니다!
✅ "전등을 켜고 끄는 기능"을 버튼으로 쉽게 조작할 수 있고, 실행 취소 기능도 간단하게 구현 가능!
💡실행 취소(Undo) 기능
👉 텍스트 편집기에서 Ctrl+Z(실행 취소) 기능 구현
👉 명령을 스택에 저장하고 undo()를 호출하면 이전 상태로 되돌림
import java.util.*
/** Command 인터페이스 */
interface Command {
fun execute()
fun undo()
}
/** Receiver (실제 텍스트를 수정하는 객체) */
class TextEditor {
var text: String = ""
fun addText(newText: String) {
text += newText
println("📝 현재 텍스트: $text")
}
fun removeText(count: Int) {
text = text.dropLast(count)
println("↩️ 실행 취소 후 텍스트: $text")
}
}
/** ConcreteCommand (텍스트 추가 명령) */
class AddTextCommand(private val editor: TextEditor, private val text: String) : Command {
override fun execute() {
editor.addText(text)
}
override fun undo() {
editor.removeText(text.length)
}
}
/** Invoker (사용자가 실행하는 인터페이스) */
class EditorInvoker {
private val commandStack = Stack<Command>()
fun executeCommand(command: Command) {
command.execute()
commandStack.push(command)
}
fun undoLastCommand() {
if (commandStack.isNotEmpty()) {
val command = commandStack.pop()
command.undo()
} else {
println("❌ 실행 취소할 명령이 없습니다.")
}
}
}
/** 클라이언트 코드 */
fun main() {
val editor = TextEditor()
val invoker = EditorInvoker()
val command1 = AddTextCommand(editor, "Hello ")
val command2 = AddTextCommand(editor, "Kotlin!")
invoker.executeCommand(command1) // 📝 현재 텍스트: Hello
invoker.executeCommand(command2) // 📝 현재 텍스트: Hello Kotlin!
invoker.undoLastCommand() // ↩️ 실행 취소 후 텍스트: Hello
invoker.undoLastCommand() // ↩️ 실행 취소 후 텍스트:
}
🔹 실행 결과
📝 현재 텍스트: Hello
📝 현재 텍스트: Hello Kotlin!
↩️ 실행 취소 후 텍스트: Hello
↩️ 실행 취소 후 텍스트:
💡 요청을 큐에 저장했다가 나중에 실행 (Job Queue)
👉 메시지 큐(MQ)나 비동기 작업 실행 시스템에서 활용
👉 웹 서버에서 사용자의 요청을 저장해두고, 특정 조건에서 실행
import java.util.*
/** Receiver (실제 작업을 수행하는 객체) */
class DataProcessor {
fun process(data: String) {
println("📊 데이터 처리 중: $data")
}
}
/** Command 인터페이스 */
interface Job {
fun execute()
}
/** ConcreteCommand (데이터 처리 작업) */
class DataProcessingJob(private val processor: DataProcessor, private val data: String) : Job {
override fun execute() {
processor.process(data) // Receiver의 메서드를 호출하여 실행
}
}
/** Invoker (Job Queue) */
class JobQueue {
private val jobQueue: Queue<Job> = LinkedList()
fun addJob(job: Job) {
jobQueue.add(job)
println("📌 작업이 큐에 추가됨: $job")
}
fun processJobs() {
while (jobQueue.isNotEmpty()) {
val job = jobQueue.poll()
job.execute()
}
}
}
/** 클라이언트 코드 */
fun main() {
val processor = DataProcessor() // Receiver 객체 생성
val queue = JobQueue()
queue.addJob(DataProcessingJob(processor, "사용자 데이터 분석"))
queue.addJob(DataProcessingJob(processor, "로그 파일 정리"))
queue.addJob(DataProcessingJob(processor, "추천 시스템 업데이트"))
println("🚀 큐에 있는 작업을 실행합니다.")
queue.processJobs()
}
🔹 실행 결과
📌 작업이 큐에 추가됨: DataProcessingJob@xxxx
📌 작업이 큐에 추가됨: DataProcessingJob@xxxx
📌 작업이 큐에 추가됨: DataProcessingJob@xxxx
🚀 큐에 있는 작업을 실행합니다.
📊 데이터 처리 중: 사용자 데이터 분석
📊 데이터 처리 중: 로그 파일 정리
📊 데이터 처리 중: 추천 시스템 업데이트
💡 매크로 기능 (여러 명령을 하나로 묶기)
👉 여러 개의 명령을 묶어서 한 번에 실행
👉 게임에서 "콤보 스킬"을 하나의 명령으로 실행하는 경우
/** Command 인터페이스 */
interface MacroCommand {
fun execute()
}
/** 개별 명령 */
class JumpCommand : MacroCommand {
override fun execute() {
println("🦘 캐릭터가 점프했습니다!")
}
}
class AttackCommand : MacroCommand {
override fun execute() {
println("⚔️ 캐릭터가 공격했습니다!")
}
}
class BlockCommand : MacroCommand {
override fun execute() {
println("🛡️ 캐릭터가 방어했습니다!")
}
}
/** Macro Command (여러 명령을 묶어서 실행) */
class ComboCommand(private val commands: List<MacroCommand>) : MacroCommand {
override fun execute() {
commands.forEach { it.execute() }
}
}
/** 클라이언트 코드 */
fun main() {
val jump = JumpCommand()
val attack = AttackCommand()
val block = BlockCommand()
val combo = ComboCommand(listOf(jump, attack, block))
println("🔥 콤보 기술 실행!")
combo.execute()
}
4️⃣ Command 패턴의 장점
✅ 1. 실행 요청과 실행 방법을 분리할 수 있다.
- Invoker(버튼)는 Receiver(전등)가 어떻게 동작하는지 몰라도 됨.
- 다른 기기(에어컨, TV)를 추가해도 Invoker 코드는 바뀌지 않음.
✅ 2. 실행 취소(Undo) 기능을 쉽게 추가할 수 있다.
- undo() 메서드를 사용하면 이전 상태로 되돌릴 수 있음.
✅ 3. 여러 개의 명령을 묶어서 실행할 수 있다. (매크로 기능)
- 여러 개의 명령을 List<Command>로 저장하고 한 번에 실행 가능.
✅ 4. 요청을 저장하고 나중에 실행할 수 있다. (Job Queue, Task Scheduling)
- 명령을 큐에 저장해 두고 특정 조건에서 실행할 수도 있음.
✅ 5. 기존 코드를 변경하지 않고 새로운 기능을 추가할 수 있다. (OCP 원칙 충족)
- Receiver(전등)나 Invoker(리모컨)을 수정하지 않고 새로운 명령을 추가 가능.
5️⃣ Command 패턴을 언제 사용할까?
상황 | Command 패턴 적용 이유 |
1. 버튼을 눌렀을 때 특정 동작 실행 | 실행과 요청을 분리하여 확장성을 높임 |
2. 실행 취소(Undo) 기능이 필요할 때 | 실행 취소를 쉽게 구현 가능 |
3. 여러 개의 명령을 조합하여 실행 (매크로 기능) | 명령을 묶어서 한 번에 실행 가능 |
4. 요청을 저장하고 나중에 실행 (Job Queue) | 메시지 큐나 작업 예약 시스템에 유용 |
🔥 결론
Command 패턴을 사용하면 "요청을 객체로 캡슐화하여 실행, 취소, 조합, 저장할 수 있음".
✔ 버튼 하나로 전등을 켜고 끄는 것처럼 쉽게 명령을 실행할 수 있음.
✔ 실행 취소(Undo), 여러 명령 묶기(매크로), Job Queue 등 다양한 곳에서 활용 가능.
➡ 즉, "명령을 관리해야 하는 경우" Command 패턴이 필수! 🚀
'Java > Spring' 카테고리의 다른 글
Spring에서 @Transactional (0) | 2025.03.12 |
---|---|
Proxy 패턴 (0) | 2025.03.12 |
[ERROR] javax.servlet.ServletRequest.getRemoteAddr() is not supported (0) | 2023.06.28 |
@Transactional 적용 안된 현상 정리 (0) | 2022.04.09 |
Controller parameter 받는 방법 (0) | 2022.04.09 |