I have a simple service manager called ServiceManager that has two methods. Create() creates an instance of a service. Provide() returns a service that has previously been created.
I have a basic implementation that works but am wondering if there's a cleaner way. This is my basic implementation of the ServiceManager:
public class ServiceManager : MonoBehaviour
{
private Dictionary<Type, MonoBehaviour> services = new Dictionary<Type, MonoBehaviour>();
public void Create<T>() where T : MonoBehaviour
{
// Create service
GameObject serviceObject = new GameObject(typeof(T).Name);
serviceObject.transform.SetParent(transform); // make service GO our child
T service = serviceObject.AddComponent<T>(); // attach service to GO
// Register service
services.Add(typeof(T), service);
}
public T Provide<T>() where T : MonoBehaviour
{
return (T)services[typeof(T)]; // notice the cast to T here
}
}
Using the service is simple:
public class ServiceTest : MonoBehaviour
{
private void Start()
{
// Creating services
ServiceManager services = FindObjectOfType<ServiceManager>();
services.Create<MapService>();
services.Create<InteractionService>();
}
private void Example()
{
// Get a service
ServiceManager services = FindObjectOfType<ServiceManager>();
MapService map = services.Provide<MapService>();
// do whatever you want with map
}
}
My question is about ServiceManager.Provide(). Notice the cast to T after getting the item from the dictionary. This feel very unclean and makes me wonder if I am missing something about how generics work in C#. Are there other/better ways to do what I am trying to accomplish?
There is nothing to improve here. The cast is necessary because the dictionary value type is a MonoBehaviour. You know that it is actually T, but the compiler doesn't. You have to tell that by casting.
You did well.
If there is only ever one instance per type, then there is better. Consider a static generic type
using UnityEngine;
public class ServiceManager : MonoBehaviour
{
// If this T confuses you from the generic T used elsewhere, rename it
public static Transform T { get; private set; }
void Awake()
{
T = transform;
}
public T Provide<T>() where T : MonoBehaviour
{
return ServiceMap<T>.service; // no cast required
}
}
static class ServiceMap<T> where T : MonoBehaviour
{
public static readonly T service;
static ServiceMap()
{
// Create service
GameObject serviceObject = new GameObject(typeof(T).Name);
serviceObject.transform.SetParent(ServiceManager.T); // make service GO our child
service = serviceObject.AddComponent<T>(); // attach service to GO
}
}
Using the service is simple:
public class ServiceTest : MonoBehaviour
{
private void Start()
{
// no need to Create services
// They will be created when Provide is first called on them
// Though if you want them up and running at Start, call Provide
// on each here.
}
private void Example()
{
// Get a service
ServiceManager services = FindObjectOfType<ServiceManager>();
MapService map = services.Provide<MapService>();
// do whatever you want with map
}
}
Also, if you have multiple ServiceManagers then this won't work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With