클린 소프트웨어 - 커맨드와 액티브 오브젝트 패턴
커맨드와 액티브 오브젝트 패턴
커맨드command 패턴은 가장 단순하면서도 세련된 패턴이다.
protocol Command { |
대부분의 클래스는 프로퍼티와 메소드의 집합으로 이루어져 있는데, 커맨드 패턴은 오히려 함수를 캡슐화하여 프로퍼티에서 자유롭게 한다.
단순한 커맨드 패턴 적용
class RelayOnCommand: Command { |
위와 같은 구조를 잘 만들어 낸다면, Command
객체를 시스템에 넘겨주는 것으로 작업을 수행할 수 있다. Command
객체가 무엇을 표현하는지 알 필요가 없이 do()
메소드를 실행할 수 있다.
위의 구체 Command 클래스를 사용하는 클라이언트는 자신이 하는 일을 모른다. 그저 Command
의 do()
메소드를 호출할 뿐이다. 즉 클라이언트는 구체적인 것에 대해 알 필요가 없다.
커맨드 패턴은 명령의 개념을 캡슐화하여 시스템의 논리적인 상호 연결을 분리해낼 수 있게 한다.
트랜잭션
커맨드 패턴은 트랜잭션의 생성과 실행에 대해 유용하게 사용될 수 있다. 예를 들어 직원 데이터베이스를 관리하는 시스템의 사용자들은 이 데이터베이스를 사용하여 새 직원을 추가하고, 기존 직원을 삭제하고, 직원의 속성을 변경할 수 있을 것이다.
protocol Transaction { |
Transaction
인터페이스를 작성하여 커맨드 패턴을 실현한다.
validate()
커맨드는 모든 데이터를 살펴보고 유효한지 확인한다.execute()
커맨드는 검증된 데이터를 사용하여 데이터베이스를 갱신한다.
물리적, 시간적 분리
위의 방식을 사용하여, 사용자에게서 데이터를 받는 코드와, 그 데이터를 검증하고 그것으로 작업을 하는 코드와, 업무 객체 그 자체를 분리할 수 있다.
예를 들어 GUI를 통해 데이터를 받을 때, 이 데이터가 유효한지 확인하고 데이터베이스 갱신 작업을 실행하는 코드가 GUI에 위치한다면, GUI와 로직이 결합되어 다른 인터페이스가 해당 로직을 사용할 수 없게 만들 것이다.
시간적 분리
커맨드를 입력하고, 그 시점에 커맨드가 유효한지 검증한 후, 나중에 작업을 수행할 수 있도록 할 수 있다.
되돌리기
protocol Command { |
Command
인터페이스의 파생 클래스가 do()
메소드가 수행하는 동작을 기억할 수 있다면, undo()
메소드가 그 동작을 되돌리도록 구현할 수 있을 것이다.
명령을 취소하는 방법을 아는 코드는 항상 그 명령을 수행하는 방법을 아는 코드와 함께 있어야 한다.
액티브 오브젝트 패턴
액티브 오브젝트 패턴은 커맨드 패턴의 응용 중 하나이며, 다중 제어 스레드 구현을 위한 기법이다.
class ActiveObjectEngine { |
protocol Command { |
ActiveObjectEngine
은 액티브 오브젝트 패턴을 구현하기 위한 클래스다. 내부에 커맨드 배열을 저장하고, 커맨드를 추가하거나 저장한 커맨드를 계속 소비하는 메소드를 포함한다.
commands
배열에 있는 요소 중 하나가 자신을 복제하여 그 복제본을 배열에 다시 넣는다면, run()
메소드는 절대 끝나지 않을 것이다.
class SleepCommand: Command { |
Command
인터페이스를 구현하는 SleepCommand
는 다음과 같이 동작한다.
- 실행이 되면, 자신이 이전에 실행된 적이 있는지 확인한다. 실행된 적이 없다면 시작 시간을 기록한다.
- 지연 시간이 지나지 않았다면 자신을 다시
ActiveObjectEngine
에 넣는다. - 지연 시간이 지났다면
ActiveObjectEngine
에wakeupCommand
명령을 넣는다.
이러한 기법을 다양하게 변형하여 멀티스레드 시스템을 구축할 수 있다. 각 Command
인스턴스가 다음 Command
인스턴스의 실행이 가능해지기 전에 완료되기 때문에, RTCrun-to-completion 태스크라는 이름으로 알려져 있다. 이는 Command
인스턴스가 블록을 하지 않는다는 의미를 내포한다.
각 RTC 스레드에 대해 별도의 런타임 스택을 정의하거나 할당할 필요가 없어, 많은 스레드가 실행되고 메모리가 제한된 시스템에서 강력한 이점이 될 수 있다.
결론
커맨드 패턴은 데이터베이스 트랜잭션, 장치 제어, 멀티스레드 시스템의 핵심, GUI에서의 실행 및 실행 취소 관리 등 매우 다양한 용도에서 사용될 수 있다.
커맨드 패턴은 클래스보다 함수를 강조하여 객체 지향 패러다임을 망가뜨린다고 이야기되어 왔으나, 현장에서는 커맨드 패턴이 매우 유용하게 사용될 수 있다.
추가
커맨드 패턴이란 요청을 객체의 형태로 캡슐화하여 사용자가 보낸 요청을 나중에 이용할 수 있도록 메소드 이름, 매개변수 등 요청에 필요한 정보를 저장 또는 로깅, 취소할 수 있게 하는 패턴이다.
커맨드 패턴에는 명령command, 수신자receiver, 발동자invoker, 클라이언트client의 네 개의 용어가 항상 따른다.
- 명령 객체는 수신자 객체를 가지고 있고, 수신자의 메소드를 호출한다.
- 수신자 객체는 명령 객체의 호출에 따라 자신에게 정의된 메소드를 수행한다.
- 발동자 객체는 명령 객체를 발동하게 한다. 명령 발동에 대한 기록을 남길 수 있다. 하나의 발동자 객체에 여러 개의 명령 객체가 전달될 수 있다.
- 클라이언트 객체는 발동자 객체와 하나 이상의 명령 객체를 보유한다. 어느 시점에서 어떤 명령을 수행할지 결정한다.
- 명령을 수행하기 위해 클라이언트 객체는 발동자 객체로 명령 객체를 전달한다.
// Invoker |