1.1 请解释 UE Subsystem 如何作为服务定位器模式,并设计一个可扩展的服务框架。
★★ 中等考点: 服务定位器模式、依赖注入、模块化
参考答案:
- 服务定位器模式:提供一个全局访问点获取服务,不暴露服务的具体实现与生命周期。UE 的 Subsystem 系统天然就是服务定位器:
GetGameInstance()->GetSubsystem<UMyService>(),无需关心创建时机与生命周期。 - Subsystem 五类生命周期:
EngineSubsystem / EditorSubsystem / GameInstanceSubsystem / WorldSubsystem / LocalPlayerSubsystem,保证单实例、随宿主自动创建销毁。 - 可扩展服务框架的核心:定义
IGameService接口(GetServiceName/Startup/Shutdown/IsReady),具体服务继承UGameInstanceSubsystem并实现接口,通过TObjectIterator<UClass>自动发现注册。 - 关键代码:
UINTERFACE(MinimalAPI)
class UGameService : public UInterface { GENERATED_BODY() };
class IGameService
{
GENERATED_BODY()
public:
virtual FName GetServiceName() const = 0;
virtual void Startup() {}
virtual void Shutdown() {}
virtual bool IsReady() const { return true; }
};
// 具体服务:继承 Subsystem + 实现接口
UCLASS()
class USaveService : public UGameInstanceSubsystem, public IGameService
{
GENERATED_BODY()
public:
virtual FName GetServiceName() const override { return FName("SaveService"); }
UFUNCTION(BlueprintCallable) bool Save(const FString& Slot, const FString& Data);
UFUNCTION(BlueprintCallable) bool Load(const FString& Slot, FString& OutData);
};
// 服务定位器:自动发现并注册服务
UCLASS()
class UServiceLocator : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
template<typename T> T* GetService() const
{
static_assert(TIsDerivedFrom<T, UGameInstanceSubsystem>::IsDerived, "...");
return GetGameInstance()->GetSubsystem<T>();
}
void DiscoverServices()
{
for (TObjectIterator<UClass> It; It; ++It)
{
if (It->IsChildOf(UGameInstanceSubsystem::StaticClass()) &&
It->GetClass()->ImplementsInterface(UGameService::StaticClass()))
{
UGameInstanceSubsystem* Sub = GetGameInstance()->GetSubsystem(*It);
TScriptInterface<IGameService> Service;
Service.SetObject(Sub); Service.SetInterface(Sub);
RegisteredServices.Add(IGameService::Execute_GetServiceName(Sub), Service);
IGameService::Execute_Startup(Sub);
}
}
}
private:
TMap<FName, TScriptInterface<IGameService>> RegisteredServices;
};- 易错点/面试官提示:理解
ShouldCreateSubsystem()条件性注册、InitializeDependency()声明服务间依赖;不要过度封装——UE 原生GetSubsystem<T>()已经足够好,服务定位器框架仅在需要按名称/条件路由时才有必要。