서비스 로케이터 패턴
서비스 로케이터 패턴은 매니저에서 주로 사용하는 방법입니다.
이 패턴은 객체가 서비스를 직접 생성하거나 찾지 않고, 중앙 집중식 위치에서 요청하고 검색하도록 합니다. 이는 객체 간의 의존성을 줄이고, 유연성과 재사용성을 증가시키기 위해 사용됩니다.
서비스 로케이터 패턴의 구조
1.
서비스 인터페이스(Service Interface) : 서비스에 대한 일반적인 인터페이스를 제공합니다.
a.
예를 들어 매니저의 경우, 초기화, 등록 등 필수적으로 구현해야 하는 부분을 인터페이스로 구현합니다.
2.
구체 서비스(Concrete Services) : 서비스 인터페이스를 구현하는 실제 서비스입니다.
3.
서비스 로케이터(Service Locator) : 클라이언트가 사용할 서비스를 찾는 데 사용되는 중앙 집중식 레지스트리입니다.
a.
게임에서 보통 사용하는 Manager 클래스가 이에 해당됩니다.
4.
클라이언트(Client): 서비스를 사용하는 객체입니다. 서비스 로케이터를 통해 필요한 서비스에 접근합니다.
서비스 로케이터 패턴 장단점
장점
1.
강한 응집력과 낮은 결합도 : 서비스의 사용자와 구현 사이의 직접적인 의존성이 줄어듭니다.
a.
게임 개발 시, 서비스 로케이터를 사용하여 오디오 서비스를 관리한다고 가정해 봅시다. 나중에 더 발전된 오디오 시스템으로 전환하고 싶을 때, 서비스 로케이터를 통해 새로운 오디오 서비스를 간단히 교체할 수 있습니다. (기존 코드를 정리하지 않아도 됨.) 이는 코드 전체를 변경할 필요 없이 중앙 집중식 위치에서 서비스를 업데이트하는 것만으로 가능합니다.
이 부분은 양날의 검이라고 생각합니다. 서비스에 대한 의존성을 간접적으로 참조함으로써, 해당 서비스가 로케이터에서 변경되면 사용하는 부분도 자동으로 변경됩니다. 하지만, 이 부분이 어떤 서비스를 참조하는지 직접적으로 나타나지 않아, 직관적이지 않을 수 있습니다.
2.
유연성 : 서비스 구현을 쉽게 교체하거나 업데이트할 수 있습니다.
a.
서비스 로케이터를 통해 필요한 서비스를 요청함으로써, 클래스 간의 결합도가 낮아지고, 각 클래스는 자신의 주요 기능에 더 집중할 수 있습니다.
3.
재사용성 및 테스트 용이성 : 단위 테스트 시 모의 객체(Mock Objects)를 쉽게 사용할 수 있습니다.
a.
테스트 시, 실제 서비스 대신 모의 객체(Mock Object)를 서비스 로케이터에 등록할 수 있습니다. 이를 통해 실제 외부 시스템에 의존하지 않고, 독립적으로 클래스나 모듈을 테스트할 수 있습니다. (이는 1번의 낮은 결합도의 기능을 활용한 방법입니다.)
단점
1.
전역 상태 : 서비스 로케이터 자체가 전역 상태를 갖게 되어, 관리가 복잡해질 수 있습니다.
a.
서비스 로케이터가 게임의 여러 부분에서 공유되고 있을 때, 한 부분에서 서비스 로케이터의 상태를 변경하면 다른 부분에도 영향을 줄 수 있습니다. 이는 예상치 못한 버그와 상태 관리의 복잡성을 초래할 수 있습니다.
b.
위 사항은 잘 발생하지 않을 수 있지만, 각자가 서비스 로케이터 패턴을 관리하는 경우에는 충돌이 일어날 수 있습니다. 예를 들어, A개발자가 특정 상황에서 오디오 서비스 로케이터를 다르게 사용하기 위해 로케이터를 교체한 경우, 다른 프로그래머가 사용하던 오디오 서비스가 A개발자의 의도에 따라 변경될 수 있습니다.
2.
코드의 명시성 감소 : 서비스의 사용이 코드에 명확히 나타나지 않아 가독성이 떨어질 수 있습니다.
a.
클래스가 서비스 로케이터를 통해 서비스를 요청할 때, 이 클래스가 어떤 서비스에 의존하고 있는지 명확하지 않을 수 있습니다. 이는 코드의 가독성을 떨어뜨리고, 시스템의 전체적인 설계를 이해하기 어렵게 만들 수 있습니다.
b.
위 내용은 서비스 로케이터 패턴을 사용하는 코드의 내부 상태를 사용하는 코드에서 바로 확인할 수 없다는 것을 의미합니다. 이는 매니저 설계 시 기본적으로 발생하는 구조이므로, 반드시 단점이라고 볼 수는 없습니다.
서비스 로케이터 패턴 UML
아래는 서비스 로케이터 패턴의 UML입니다.
서비스 로케이터 패턴 구현
서비스 로케이터 패턴을 사용하는 간단한 예시를 유니티 C# 코드로 설명하겠습니다.
이 예제에서는 간단한 오디오 서비스를 만들고, 서비스 로케이터를 통해 이를 관리하는 방법을 보여줍니다.
1. 서비스 인터페이스 정의
먼저, 서비스에 대한 인터페이스를 정의합니다. 이 인터페이스는 모든 구체적인 서비스가 구현해야 하는 메소드를 선언합니다.
public interface IAudioService
{
void PlaySound(string soundName);
void StopSound(string soundName);
}
C#
복사
2. 구체적인 서비스 구현
이제 인터페이스를 구현하는 실제 서비스를 만듭니다. 여기서는 SimpleAudioService라는 구체적인 서비스 클래스를 만들겠습니다.
public class SimpleAudioService : IAudioService
{
public void PlaySound(string soundName)
{
Debug.Log($"Playing sound: {soundName}");
// 여기에 실제 사운드 재생 로직 추가
}
public void StopSound(string soundName)
{
Debug.Log($"Stopping sound: {soundName}");
// 여기에 실제 사운드 중지 로직 추가
}
}
C#
복사
3. 서비스 로케이터 구현
서비스 로케이터는 서비스에 대한 참조를 관리하고, 필요에 따라 서비스를 제공합니다.
public class ServiceLocator
{
private static IAudioService _audioService;
public static IAudioService AudioService
{
get { return _audioService; }
set { _audioService = value; }
}
public static void ProvideAudioService(IAudioService service)
{
_audioService = service;
}
}
C#
복사
4. 서비스 등록 및 사용
마지막으로, 게임의 초기 설정 단계에서 서비스를 서비스 로케이터에 등록하고, 필요할 때 이를 사용합니다.
// 게임 초기화 시
ServiceLocator.ProvideAudioService(new SimpleAudioService());
// 서비스 사용
ServiceLocator.AudioService.PlaySound("explosion");
ServiceLocator.AudioService.StopSound("backgroundMusic");
C#
복사