언어/디자인 패턴

[디자인 패턴]#14_서비스 중개자 패턴, Service Locator

Hardii2 2022. 10. 9. 12:43

 

[디자인 패턴] #14_서비스 중개자 패턴, Service Locator

 

게임 디자인 패턴 중 "디커플링 패턴"에 대해 알아보겠습니다.

"게임 프로그래밍 패턴"의 16 항목, "서비스 중개자"에 해당하는 내용입니다.

 

 


 

Overview

 

  1. 개요
  2. 활용
  3. 예제
  4. 널 객체 디자인 패턴
  5. 데코레이터 패턴

 

#1. 개요

1. 정의

  • 서비스 중개자 패턴은 디자인 패턴 중 하나로, 다수의 서비스나 컴포넌트가 서로 통신할 수 있도록 '중개자'를 도입하는 방법입니다. 따라서, '서비스 중개자' 패턴은 객체 간의 커플링을 낮추고, 유연성을 향상하며, 유지보수성을 증가시키는데 도움이 됩니다.

 

2. 왜 필요한가?

// 정적 서비스 클래스
AudioSystem::playSound(GUN_SHOT_SOUND);

// 싱글턴 디자인 패턴 활용
AudioSystem::getInstance()->playSound(GUN_SHOT_SOUND);

 

Details

 

  • 서비스 클래스는 코드 전역에서 필요로 하기 때문에, 보통은 "정적 클래스"로 선언하거나, "싱글턴"을 활용합니다.
  • 하지만, 이러한 방법들은 "강한 커플링"을 동반하며, 문제 발생시 수정이 용이하지 않겠죠!
  • 따라서, 요청 하는 사람과 요청받는 사람이 모두 숨겨지며, 한 곳에서 편리하게 관리할 수 있도록 "서비스 중개자"를 활용합니다!

 

#2. 활용

1. 기본 구조

  1. "서비스" 추상 인터페이스 클래스
  2. "서비스" 인터페이스 클래스를 상속하는 "서비스 제공" 구체 클래스
  3. "서비스" 구체 클래스의 등록과 제공 과정에서 해당 클래스의 정확한 자료형을 숨겨주는 "서비스 중개자" 클래스

 

2. 사용 시점

  • 전역 메커니즘 해결을 위해 객체를 인수로 넘겨주는 방식을 보통 채택합니다.
  • 하지만, 이러한 방식은 코드 가독성을 떨어뜨리고, 복잡성을 증가시킵니다.
  • "서비스 중개자" 패턴은 절제하는 것 또한 필요하지만, 유연한 싱글턴 패턴의 필요 요건을 충분히 만족시킵니다!

 

3. 주의 사항

  1. 서비스 클래스를 별도로 "서비스 중개자"에 등록해야 합니다. 
  2. 서비스 요청자의 정보가 노출되지 않기 때문에, 접근성에 대한 고찰이 필요합니다.

 

#3. 예제

1. 서비스 인터페이스 클래스, AudioSystem

class AudioSystem
{
public:
	virtual ~AudioSystem();

	virtual void PlaySound(int SoundID) = 0;
	virtual void StopSound(int SoundID) = 0;
};

 

2. 서비스 구체 클래스, ConsoleAudioSystem

class ConsoleAudioSystem : public AudioSystem
{
public:
	virtual void PlaySound(int SoundID)
	{
		// 정의
	}
	
	virtual void StopSound(int SoundID)
	{
		// 정의
	}
    //...
};

 

3. 서비스 중개자, Locator

class Locator
{
public:
  	 // 서비스 등록 메서드
	static void registerAuioSystem(AudioSystem* _service) { service = _service; }
	// 서비스 제공 메서드
	static AudioSystem* getAudioSystem() { return service; }

private:
	static AudioSystem* service;
};

 

Details

 

  • 멤버 메서드와 데이터 멤버 모두 "static"으로 선언한 것이 주요합니다!

 

4. 서비스 등록 예제 코드

ConsoleAudioSystem* consoleAudio = new ConsoleAudioSystem();
Locator::registerAudioSystem(consoleAudio);

 

 

5. 서비스 요청 반환 예제 코드

AudioSystem* audio = Locator::getAudioSystem();
audio->PlaySound(GUN_SHOT_SOUND);

 

Details

 

  • AudioSystem 클래스는 인터페이스 클래스로, 서비스 제공 구체 클래스의 자료형이 은닉됩니다.
  • 따라서, 요청 받는 곳과 서비스 구체 클래스 간 디커플링이 활성화됩니다.

 

#4. 널 객체 디자인 패턴

1. 단점

  • 요청 받은 서비스 객체가 아직 "서비스 중개자"에 미등록된 상태라면 NULL 객체가 반환되는 문제가 발생합니다.
  • 이때, 우리는 "널 객체 디자인 패턴"을 활용합니다.

 

2. NULL 객체 디자인 패턴

// 1. 널 객체
class NullAudioSystem : public AudioSystem
{
public:
	virtual void PlaySound(int SoundID)
	{
		// 아무런 기능을 제공하지 않습니다.
	}

	virtual void StopSound(int SoundID)
	{
		// 아무런 기능을 제공하지 않습니다.
	}
};

// 2. 서비스 중개자 클래스
class Locator
{
public:
	static void Init()
	{
		service = &nullService;
	}
	
	static void getAudioSystem() { return *service; }

	static void registerAudioSystem(AudioSystem* service_)
	{
		if (service_ == NULL)
		{
			service = &nullService;
		}
		else
		{
			service = service_;
		}
	}

private:
	static AudioSystem* service;
	static NullAudioSystem* nullService;
};

 

Details

 

  • "NullAudioSystem" 클래스는 AudioSystem 인터페이스 클래스를 상속받지만, 아무 기능도 제공하지 않습니다.
  • Locator 클래스는 이러한 "NullAudioSystem" 객체 포인터를 멤버로 두어, 상세 기능을 제공하는 서비스 구체 클래스의 객체가 미등록 상태에선 항상 NullAudioSystem 객체 포인터를 반환하도록 합니다.
  • 이로써, 서비스 중개자 클래스는 항상 유효한 객체를 반환합니다.

 

#5. 데코레이터 패턴

1. 개념

  • Decorator란, 어떠한 동작의 생명 주기 동안, 백그라운드에서 함께 수행되는 동작입니다.
  • 기생하는 특정 동작의 수행이 모두 끝나면, 함께 끝납니다.

 

2. LoggedAudio, 데코레이션 클래스

class LoggedAudio : public AudioSystem
{
public:
	LoggedAudio(AudioSystem& _wrappedByDecorator) : wrappedByDecorator(_wrappedByDecorator){}

	virtual void PlaySound(int SoundID)
	{
		// 데코레이터 클래스의 로깅 메서드 호출
		log("사운드 재생");

		// 감싼 서비스 클래스의 멤버 메서드를 호출
		wrappedByDecorator.PlaySound(SoundID);
	}

	virtual void StopSound(int SoundID)
	{
		// 데코레이터 클래스의 로깅 메서드 호출
		log("사운드 정지");

		// 감싼 서비스 클래스의 멤버 메서드를 호출
		wrappedByDecorator.StopSound(SoundID);
	}

private:
	// 로깅 메서드
	void log(string str);

	AudioSystem& wrappedByDecorator;
};

 

4. EnableLogging(), 데코레이터 클래스 생성

void EnableLogging()
{
	// 서비스 중개자에 등록된 기존의 서비스 클래스를 LoggedAudio 데코레이터로 감싸줍니다.
	AudioSystem* service = new LoggedAudio(Locator::getAudioSystem());
	
	// 기존 서비스 + 데코레이터 객체로 서비스 중개자에 재등록
	Locator::registerAudioSystem(service);
}