Created
April 6, 2024 20:54
-
-
Save altunsercan/8206692c72545dbf6997a0b3e174c9de to your computer and use it in GitHub Desktop.
Simple single file service locator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Dependency; | |
public static class ServiceLocator | |
{ | |
private static BaseServiceLocator instance; | |
private static BaseServiceLocator GetInstance() | |
{ | |
return instance ??= new BaseServiceLocator(); | |
} | |
public static T Resolve<T>() => GetInstance().Resolve<T>(); | |
public static void RegisterProvider<T>(Func<T> providerDelegate) => GetInstance().RegisterProvider(providerDelegate); | |
public static void RegisterProvider<T>(Func<BaseServiceLocator, T> providerDelegate) => GetInstance().RegisterProvider(providerDelegate); | |
public static void RegisterCached<T>(T instance) => GetInstance().RegisterCached(instance); | |
} | |
public class BaseServiceLocator | |
{ | |
private Dictionary<Type, Provider> providers = new (); | |
public void RegisterProvider<T>(Func<T> providerDelegate) | |
{ | |
ThrowIfNullProvider(providerDelegate, typeof(T)); | |
ThrowIfProviderExists(providers, typeof(T)); | |
providers.Add(typeof(T), Provider.Create(providerDelegate)); | |
} | |
public void RegisterProvider<T>(Func<BaseServiceLocator, T> providerDelegate) | |
{ | |
ThrowIfNullProvider(providerDelegate, typeof(T)); | |
ThrowIfProviderExists(providers, typeof(T)); | |
providers.Add(typeof(T), Provider.Create(providerDelegate)); | |
} | |
public void RegisterCached<T>(T instance) | |
{ | |
var cachedProvider = () => instance; | |
RegisterProvider(cachedProvider); | |
} | |
public T Resolve<T>() | |
{ | |
var provider = providers[typeof(T)]; | |
return provider.Resolve<T>(serviceLocator: this); | |
} | |
private static void ThrowIfNullProvider(object providerDelegate, Type typeName) { | |
if (providerDelegate == null) | |
{ | |
throw new NullProviderException(typeName); | |
} | |
} | |
private static void ThrowIfProviderExists(Dictionary<Type, Provider> providers, Type type) { | |
if (providers.ContainsKey(type)) | |
{ | |
throw new DuplicateProviderException(type); | |
} | |
} | |
private struct Provider | |
{ | |
private object untypedDelegate; | |
private Provider(object untypedDelegate) | |
{ | |
this.untypedDelegate = untypedDelegate; | |
} | |
public static Provider Create<T>(Func<T> providerDelegate) => new(providerDelegate); | |
public static Provider Create<T>(Func<BaseServiceLocator, T> providerDelegate) => new(providerDelegate); | |
public T Resolve<T>(BaseServiceLocator serviceLocator) | |
{ | |
if (untypedDelegate is Func<BaseServiceLocator, T> typedDelegateWithLocator) | |
{ | |
return typedDelegateWithLocator.Invoke(serviceLocator); | |
} | |
if (untypedDelegate is Func<T> typedDelegate) | |
{ | |
return typedDelegate.Invoke(); | |
} | |
throw new ProviderTypeResolutionException(typeof(T)); | |
} | |
} | |
} | |
public class ProviderTypeResolutionException : Exception | |
{ | |
public readonly Type Type; | |
public ProviderTypeResolutionException(Type type) | |
{ | |
Type = type; | |
} | |
public override string Message => $"Cannot register null provider"; | |
} | |
public class NullProviderException : Exception | |
{ | |
public readonly Type Type; | |
public NullProviderException(Type type) | |
{ | |
Type = type; | |
} | |
public override string Message => $"Cannot register null provider"; | |
} | |
public class DuplicateProviderException : Exception | |
{ | |
public readonly Type Type; | |
public DuplicateProviderException(Type type) | |
{ | |
Type = type; | |
} | |
public override string Message => $"Cannot register duplicate provider"; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Dependency; | |
using FluentAssertions; | |
using NSubstitute; | |
using NUnit.Framework; | |
namespace Test; | |
public class ServiceLocatorTest | |
{ | |
[SetUp] | |
public void Setup() | |
{ | |
} | |
[Test] | |
public void InvokeServiceLocatorProviderWithNoDependency() | |
{ | |
var mockProviderDelegate = Substitute.For<Func<ITestFoo>>(); | |
var serviceLocator = new BaseServiceLocator(); | |
serviceLocator.RegisterProvider(mockProviderDelegate); | |
ITestFoo foo = serviceLocator.Resolve<ITestFoo>(); | |
Assert.NotNull(foo); | |
} | |
[Test] | |
public void InvokeServiceLocatorProviderWithDependencies() | |
{ | |
var mockDependencyProviderDelegate = Substitute.For<Func<BaseServiceLocator, ITestBar>>(); | |
Func<BaseServiceLocator, ITestFoo> mockProviderDelegate = (locator)=> new TestFooWithBar(locator.Resolve<ITestBar>()); | |
var serviceLocator = new BaseServiceLocator(); | |
serviceLocator.RegisterProvider(mockDependencyProviderDelegate); | |
serviceLocator.RegisterProvider(mockProviderDelegate); | |
ITestFoo foo = serviceLocator.Resolve<ITestFoo>(); | |
Assert.NotNull(foo); | |
} | |
[Test] | |
public void DontAllowMultipleProvidersForSameType() | |
{ | |
Func<ITestFoo> providerDelegate = Substitute.For<Func<ITestFoo>>(); | |
Func<ITestFoo> providerDelegate2 = Substitute.For<Func<ITestFoo>>(); | |
var serviceLocator = new BaseServiceLocator(); | |
serviceLocator.RegisterProvider<ITestFoo>(providerDelegate); | |
Assert.Throws<DuplicateProviderException>(() => | |
{ | |
serviceLocator.RegisterProvider(providerDelegate2); | |
}); | |
} | |
[Test] | |
public void ShouldNotAllowNullProviders() | |
{ | |
var serviceLocator = new BaseServiceLocator(); | |
Func<ITestFoo> provider = null; | |
Assert.Throws<NullProviderException>(() => | |
{ | |
serviceLocator.RegisterProvider(provider); | |
}); | |
Func<BaseServiceLocator, ITestFoo> providerWithDependency = null; | |
Assert.Throws<NullProviderException>(() => | |
{ | |
serviceLocator.RegisterProvider(providerWithDependency); | |
}); | |
} | |
[Test] | |
public void RegisterSingletonProvider() | |
{ | |
var mockFoo = Substitute.For<ITestFoo>(); | |
var serviceLocator = new BaseServiceLocator(); | |
serviceLocator.RegisterCached(mockFoo); | |
ITestFoo foo = serviceLocator.Resolve<ITestFoo>(); | |
foo.Should().Be(mockFoo); | |
} | |
} | |
public interface ITestBar { } | |
public interface ITestFoo { }public class TestFooWithBar : ITestFoo | |
{ | |
private readonly ITestBar bar; | |
public TestFooWithBar(ITestBar bar) | |
{ | |
this.bar = bar; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment