Skip to content

Latest commit

 

History

History
51 lines (35 loc) · 5.77 KB

File metadata and controls

51 lines (35 loc) · 5.77 KB

명령

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

💡 책에서 설명하는 의도

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

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

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

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

🛠 활용성: 이럴 때 씁니다

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

🎁 결과

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

🗺 구현 방법

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

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

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

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

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