디자인패턴

[디자인 패턴] - Service Locator (Unreal5)

KimGeon-U 2025. 3. 17. 21:42

https://www.geeksforgeeks.org/service-locator-pattern/

 

Service Locator

  • Locator 클래스가 서비스 객체의 인스턴스를 관리하고 제공
  • 클라이언트는 서비스의 구현을 직접 참조하지 않고 Locator를 통해 서비스에 접근
  • 모듈화 수준을 높이며 클라이언트와 인터페이스 사이의 의존성을 제거한다.
  • Factory패턴과 유사하지만, Locator는 이미 생성된 객체를 제공한다.
  • 자주 참조하는 객체를 가져올 때 사용할 수 있다.
    • 멀티플레이 Server-Client 구조의 Network(Locator)를 사용한 서비스 접근 방식
    • ActorComponent를 사용하여 응집도를 높이며 Actor들을 관리하는 방식

장점 및 단점

  • 런타임 최적화 : 더 최적화된 라이브러리 또는 컴포넌트를 동적으로 감지하여 최적화 가능
  • 구현하기 쉽다.
  • Mock 객체 주입이 불가능하여 단위테스트 하기 어렵다.
  • ServiceLocator를 직접 참조하는 형태로, 결합도 또한 높다.

 

예시코드

WeaponComponent.cpp

// 장지 장착 로직
void UWeaponComponent::EquipWeapon(EWeaponType Slot, TSubclassOf<class ABaseWeapon> WeaponClass)
{
	ACharacter* TargetOwner = Cast<ACharacter>(GetOwner());

	if (!TargetOwner || !WeaponClass)
	{
		UE_LOG(LogWeapon, Error, TEXT("WeaponComponent_EquipWeapon Func Owner || WeaponClass nullptr"));
		return;
	}

	if (EquipWeapons.Contains(Slot) && EquipWeapons[Slot])
	{
		EquipWeapons[Slot]->Destroy();
		EquipWeapons[Slot] = nullptr;
	}

	FActorSpawnParameters SpawnParams;
	SpawnParams.Owner = TargetOwner;
	SpawnParams.Instigator = TargetOwner->GetInstigator();
	ABaseWeapon* NewWeapon = GetWorld()->SpawnActor<ABaseWeapon>(WeaponClass, SpawnParams);

	if (NewWeapon)
	{
		NewWeapon->AttachToComponent(TargetOwner->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true),
			WeaponAttachSocketName);
		NewWeapon->SetActorRelativeLocation(NewWeapon->GetSocketOffset());
		NewWeapon->SetActorRelativeRotation(NewWeapon->GetSocketRotation());
		NewWeapon->SetActorEnableCollision(false);

		EquipWeapons.Add(Slot, NewWeapon);
		SwitchWeapon(Slot);
	}
}
// 장비 교체 로직
void UWeaponComponent::SwitchWeapon(EWeaponType Slot)
{
	ACharacter* TargetOwner = Cast<ACharacter>(GetOwner());

	if (!TargetOwner || !EquipWeapons.Contains(Slot))
	{
		UE_LOG(LogWeapon, Error, TEXT("WeaponComponent_SwitchWeapon Func Owner || Target Weapon Slot is nullptr"));
		return;
	}

	if (CurrentWeapon)
	{
		UnEquipWeapon();
	}

	CurrentWeapon = EquipWeapons[Slot];
	if (CurrentWeapon)
	{
		CurrentWeapon->SetActorHiddenInGame(false);
	}
}

 

Player.h

	// Weapons 
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon")
	UWeaponComponent* WeaponComponent;
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon")
	TSubclassOf<class ABaseWeapon> WeaponClass;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon")
	TSubclassOf<class ABaseWeapon> SubWeaponClass;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon")
	TSubclassOf<class ABaseWeapon> MeleeWeaponClass;

Player.cpp

void APlayer::SpawnActorComponent()
{
	WeaponComponent = CreateDefaultSubobject<UWeaponComponent>(TEXT("Weapon"));
	if (!WeaponComponent)
	{
		UE_LOG(LogPlayer, Error, TEXT("WeaponComponent CDO Fail"));
	}
}

// TODO :Event 형식으로 UI와 연동, 해당 WeaponClass를 WeaponComponent에 추가 요청.
void APlayer::AttachWeapon()
{
	if (WeaponComponent)
	{
		WeaponComponent->EquipWeapon(EWeaponType::Rifle, WeaponClass);
		WeaponComponent->EquipWeapon(EWeaponType::Pistol, SubWeaponClass);
		WeaponComponent->EquipWeapon(EWeaponType::Melee, MeleeWeaponClass);
	}
}

// 장비 변경
void APlayer::SwitchCurrentWeapon(int32 WeaponType)
{
	UE_LOG(LogPlayer, Warning, TEXT("WeaponType Call : %d"), int32(WeaponType));

	if (WeaponComponent)
	{
		WeaponComponent->SwitchWeapon((EWeaponType)WeaponType);
	}
}

 

예시코드 결과

  • WeaponComponent(Locator)를 통해 Weapon 관련 클래스들을 제공하여 응집도를 높일 수 있다.
  • Player - Weapon 클래스간 낮은 결합도를 유지할 수 있다.
  • Weapon(서비스) 관리가 용이하며, 해당 클래스를 단순하게 사용할 수 있다.
  • WeaponComponent가 Player 클래스에 직접 주입되는 형태로, 완전한 Service Locator가 아닌 상태.