서비스 중개자 패턴
서비스를 구현한 구체 클래스는 숨긴 채로 어디에서나 서비스에 접근할 수 있도록 한다.
메모리 할당, 난수 생성 코드나 오디오 시스템 등은 다양한 시스템에서 쓰인다.
이런 시스템은 게임 전체에서 사용 가능해야 하는 서비스로 볼 수 있다.
// 1. 정적 클래스
AudioSystem::playSound(VERY_LOUD_BANG);
// 2. 싱글턴
AudioSystem::instance()->playSound(VERY_LOUD_BANG);
둘 다 어디서든 호출할 수 있는 방법이지만 강한 커플링이 생긴다.
AudioSystem
이라는 구체 클래스뿐만 아니라, 정적 클래스/싱글턴으로 만든 접근 메커니즘까지 직접 참조하기 때문이다.
서비스 중개자 패턴은 어떤 시스템이 구체 클래스에 바로 접근하지 않고 중개자를 통해 구체 클래스에 접근하도록 한다.
(책에서는 직접 주소를 알려주는 게 아니라 전화번호부에서 이름으로 주소를 찾는다고 비유함)
-> 서비스를 사용할 시스템은 서비스를 구현한 구체 클래스 자료형이 무엇인지, 클래스 인스턴스를 어떻게 얻는지 몰라도 된다.
?
싱글턴이나 정적 클래스는 인스턴스가 항상 존재한다.
그러나 서비스 중개자 패턴에서는 서비스 객체를 들옥해야 하므로, 필요한 객체가 없을 때를 대비해야 한다.
전역에서 서비스 중개자로 접근 가능하기 때문에, 서비스는 어느 환경에서나 문제없이 동작해야 한다.
따라서 어떤 환경에서는 사용하면 안된다면, 서비스로는 적합하지 않다. 이런 클래스는 서비스 중개자 패턴을 적용하지 말자.
15장 오디오 시스템 예제 참고
// 오디오 API
class Audio {
public:
virtual ~Audio() {}
virtual void playSound(int soundID) = 0;
virtual void stopSound(int soundID) = 0;
virtual void stopAllSounds() = 0;
};
추상 인터페이스로 서비스를 만들었다.
class ConsolAudio : public Audio {
public:
virtual void playSound(int soundID) {
// 콘솔의 오디오 API를 이용해 사운드 출력
}
virtual void stopSound(int soundID) {
// 콘솔의 오디오 API를 이용해 사운드 중지
}
virtual void stopAllSounds(int soundID) {
// 콘솔의 오디오 API를 이용해 모든 사운드 중지
}
};
구체 클래스까지 구현했다.
이제 인터페이스와 클래스를 묶어주는 서비스 중개자를 만들어야 한다.
class Locator {
public:
static Audio* getAudio() { return service_; }
static void provide(Audio* service) { service_ = service; }
private:
static Audio* service_;
};
// 서비스 제공자를 외부에서 등록
ConsoleAudio* audio = new ConsoleAudio();
Locator::provide(audio);
// 서비스 중개
Audio* audio = Locator::getAudio();
audio->playSound(VERY_LOUD_BANG);
provide
: 서비스 제공자를 등록
getAudio
: 중개 역할. Audio
서비스 인스턴스를 반환