Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] 명령 패턴 내용 정리 #90

Open
wants to merge 1 commit into
base: study/design-pattern
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions study/design-pattern/catalogs/command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 명령

a.k.a. 작동(Action), 트랜잭션(Transaction)

## 💡 책에서 설명하는 의도

"요청 자체를 캡슐화하는 것입니다. 이를 통해 요청이 서로 다른 사용자를 매개변수로 만들고, 요청을 대기시키거나 로깅하며, 되돌릴 수 있는 연산을 지원합니다."

## 🧐 우리 상황에 맞게 풀어 쓴 동기

어떤 연산은 그 연산을 누가 실행할지 모를 때가 있습니다. 예를 들어, 입력한 정보를 "저장"한다고 해봅시다. UI에서는 저장 명령을 내리고 싶지만, 그 정보가 어디에 저장되는지는 모릅니다. 서버에 요청을 보낼 수도 있고, 로컬 스토리지에 저장할 수도 있고, IndexedDB에 저장할 수도 있습니다. 심지어, 스펙이 변경됨에 따라 그 연산을 처리하는 객체가 완전히 달라질 수도 있습니다.

이럴 때 사용되는 패턴이 명령(command) 패턴입니다. 기본적으로 명령 패턴은 요청을 객체로 바꿈으로써 실제 연산을 숨깁니다. 실제 연산에 필요한 참조 객체나 콜백은 매개변수로 전달받아 내부에 인스턴스로 저장하고, 객체의 실행(`execute()`) 메서드가 실행될 때 내부의 인스턴스를 사용합니다.

## 🛠 활용성: 이럴 때 씁니다

- 수행할 동작을 객체로 매개변수화하고자 할 때. 절차지향 프로그래밍에서의 콜백 함수(어딘가 등록되었다가 나중에 실행되는 함수)를 객체지향 방식으로 표현한 것이 명령 패턴입니다.
- 서로 다른 시간에 요청을 명시하고, 저장하고, 실행하고 싶을 때.
- 실행 취소 기능을 지원하고 싶을 때. 명령 객체의 `execute` 연산을 반대로 하여 `unexecute` 연산을 만들고, 이를 사용하게 할 수 있습니다.
- 연산 과정 및 변경 과정을 로깅하고 싶을 때. 명령 인터페이스를 확장해 `load`와 `store` 연산을 정의하면, 상태의 변화를 저장소에 저장할 수 있습니다. 시스템에 문제가 발생했을 때 저장된 명령을 읽어 다시 실행하게 할 수 있습니다.
- 기본적인 연산을 조합한 상위 수준 연산(트랜잭션)을 정의해 시스템을 구조화하고 싶을 때. 명령 패턴은 `execute`를 실행하는 일관된 인터페이스를 제공하므로, 어떤 트랜잭션을 만들든 복잡도와 상관 없이 동일한 인터페이스를 제공합니다.

## 🎁 결과

1. 연산을 호출하는 객체와 연산 수행 방법을 구현하는 객체를 분리합니다.
- 메쉬원의 `Store`와 `Service`를 생각해봅시다. 호출하는 객체는 `Store`지만, 실제 수행 방법은 `Service`에 추상화됩니다.
2. Command는 일급 객체입니다. 다른 객체와 같은 방식으로 조작되고 확장할 수 있습니다.
- 필요한 경우 생성된 커맨드를 여러 객체에서 공유할 수 있고, 상속으로 확장해 사용할 수도 있습니다.
3. 명령을 여러 개를 복합해서 복합 명령을 만들 수 있습니다.
- 예를 들어, 우리는 상점을 생성할 때 상점을 생성한 후 ID를 전달받아 요금제 정보를 저장하는 등 일련의 트랜잭션이 필요할 때, 해당 명령을 전부 `StoreService`의 `addStore` 메서드에 캡슐화하고 있습니다. 서버에 여러 번 호출해 데이터를 가져오는 트랜잭션 역시 마찬가지입니다.
4. 새로운 커맨드 객체를 추가하기 쉽습니다. 기존 클래스를 변경할 필요 없이 단지 새로운 명령어에 대응하는 클래스만 정의하면 됩니다.
- 정석적으로 구현한다면, 새로운 명령을 추가할 때 클래스 변경 대신 새로운 객체를 추가만 하면 되므로 사이드 이펙트 걱정 없이 쉽고 빠르게 명령을 추가할 수 있습니다.

## 🗺 구현 방법

### 객체지향에서 구현하는 정석적인 방법

1. 명령을 수행하기 위한 클래스를 정의하고, 생성자 레벨에서 필요한 객체를 전달받습니다.
2. `execute` 메서드를 만들어 연산을 실행하기 위한 인터페이스를 만들고, 메서드 내에서 연산을 실행합니다.
- 취소(`undo`) 및 반복(`redo`) 연산을 지원하기 위해 추가적으로 이력 목록을 저장해야 할 수 있습니다. 실행한 목록을 반대로 읽으면 실행 취소가 구현됩니다.
- 명령어를 처리하거나 취소하면서 오류가 발생할 수 있습니다. 따라서 명령어를 취소했을 때의 상태가 원래의 상태와 같은지 확인하는 작업이 필요할 수 있습니다.
3. 필요한 경우 `store`나 `load` 메서드를 만들어, 명령의 수행 정보를 저장할 수 있습니다.

## 🔙 우리가 사용한 예시 (또는 우리가 사용했다면...)

이 패턴은 우리에게 다른 어떤 패턴보다도 익숙한 패턴입니다.

1. 요청의 추상화 측면에서, 우리 설계의 서비스는 스토어에서 실행하는 요청을 추상화한 명령 메서드의 집합이라고 볼 수 있습니다.
2. 검증 인터페이스의 통일 및 추상화 측면에서, 커맨드 폴더의 Cake Form 객체들은 일종의 명령 객체입니다.
3. 뷰 입장에서, 명령을 추상화한 모든 사례들은 일종의 명령 패턴이라고 볼 수 있습니다. 예를 들면 Redux나 MobX의 액션은 기본적으로 명령입니다. Time-traveling debugging을 통한 undo/redo의 편의성이나 로깅 등을 봤을 떄, Redux의 명령들은 훌륭한 명령이라고 볼 수 있겠습니다.
4. 서버에서도 명령 패턴을 자주 사용합니다. 복잡한 트랜잭션이나 대량 처리를 하나의 메서드로 관리해 실행만 시키고, 실행 결과는 별도의 요청으로 받아보게 하는 패턴은 풀필먼트나 OMS 등 대량 처리가 많은 곳에서 흔하게 보셨을 것입니다.