【问题标题】:Generic Static Class as Service Locator通用静态类作为服务定位器
【发布时间】:2017-12-08 06:22:24
【问题描述】:

我将服务定位器模式应用为described in Game Programming 模式,并且想知道可能的通用实现。以下代码确实有效,但我对使用通用和静态的类感到困惑。

以下 C# 代码的想法是为应用程序的其他部分提供“全局”服务,只公开一个接口而不是完整的实现。使用此方法注册的每个服务在应用程序中只有一个实例,但我希望能够轻松地换入/换出所提供接口的不同实现。

我的问题是:当我使用以下类在整个应用程序中提供不同的服务时,C# 怎么知道我指的是不同类型的不同服务?直觉上,我几乎认为静态变量 _service 会被每个新服务覆盖。

public static class ServiceLocator<T>
{
    static T _service;

    public static T GetService()
    {
        return _service;
    }

    public static void Provide(T service)
    {
        _service = service;
    }
}

这里有一些用法:

// Elsewhere, providing:
_camera = new Camera(GraphicsDevice.Viewport);
ServiceLocator<ICamera>.Provide(_camera);

// Elsewhere, usage:
ICamera camera = ServiceLocator<ICamera>.GetService();

// Elsewhere, providing a different service:
CurrentMap = Map.Create(strategy);
ServiceLocator<IMap>.Provide(CurrentMap);

// Elsewhere, using this different service:
IMap map = ServiceLocator<IMap>.GetService();

【问题讨论】:

  • public static class ServiceLocator&lt;T&gt; 这意味着你有很多封闭类型的ServiceLocator'1。因此,如果您使用ServiceLocator&lt;ICamera&gt;ServiceLocator&lt;IMap&gt;,您将拥有两个static T _service; 对象。你是对的,当你使用ServiceLocator&lt;T&gt;.Provide 时,它会重写`_service;` 的值,但它只会重写封闭类型ServiceLocator'1 的值。这意味着ServiceLocator&lt;IMap&gt;.Provide(CurrentMap) 将重写static IMap _service;,但在上述情况下不会重写static ICamera _service;
  • 我有一个通用服务定位器,用于依赖注入不可用的情况。明天我有工作电脑后会发布。
  • @ScottHannen 谢谢,那太好了!

标签: c# oop design-patterns static monogame


【解决方案1】:

C# 为开放类型的每个泛型参数组合创建一个单独的封闭类型。
由于泛型参数的每个组合都会创建一个单独的类,调用静态构造函数并为它们中的每一个创建自己的成员。 你可以把它们想象成不同的类。

public static class GenericCounter<T>
{
    public static int Count { get; set; } = 0;
}

GenericCounter<int>.Count++;
GenericCounter<int>.Count++;
GenericCounter<string>.Count++;
Console.WriteLine(GenericCounter<double>.Count); // 0
Console.WriteLine(GenericCounter<int>.Count); // 2
Console.WriteLine(GenericCounter<string>.Count); // 1

此代码输出:

0
2
1

例如,在您的情况下,行为将相同,就好像您创建了两个单独的类:

public static class ServiceLocatorOfIMap
{
    static IMap _service;

    public static IMap GetService()
    {
        return _service;
    }

    public static void Provide(IMap service)
    {
        _service = service;
    }
}

public static class ServiceLocatorOfICamera 
{
    static ICamera _service;

    public static ICamera GetService()
    {
        return _service;
    }

    public static void Provide(ICamera service)
    {
        _service = service;
    }
}

并像这样使用它:

// Elsewhere, providing:
_camera = new Camera(GraphicsDevice.Viewport);
ServiceLocatorForICamera.Provide(_camera);

// Elsewhere, usage:
ICamera camera = ServiceLocatorForICamera.GetService();

// Elsewhere, providing a different service:
CurrentMap = Map.Create(strategy);
ServiceLocatorForIMap.Provide(CurrentMap);

// Elsewhere, using this different service:
IMap map = ServiceLocatorForIMap.GetService();

总的来说,它类似于C#遇到静态泛型类时所做的事情。

【讨论】:

  • 太好了!感谢您的解释。我认为它按照这些思路工作,但想确保我不会因为误解而开始自掘坟墓。
【解决方案2】:

我将它用于无法一直使用依赖注入(如 WebForms)但我想编写由 DI 容器解析的可测试类的情况。

用法看起来像

using(var resolved = new ResolvedService<ISomeService>())
{
    resolved.Service.DoSomething();
}

好的:

  • 您可以使用 DI 容器来解析和释放资源
  • 它是一次性的,disposing 会导致容器释放资源
  • 它使容器和服务注册不可见

坏处:

  • 它需要一个静态类,但它也在组合根中,所以还不错。
  • 正如所写,它直接取决于温莎。用任何其他容器替换它很容易。也许有一天我会把它拆开,这样ServiceLocator 就不会与任何特定的容器耦合。但现在改变这一点是微不足道的。

使用这意味着虽然较大的组件(如 .aspx 页面)不可测试,但我注入其中的内容是可测试的。它只是给了我一个疯狂的想法——我可以为 WebForms 页面编写编排器,以便它们大部分是可测试的。但希望我永远不需要那样做。

internal class ServiceLocator
{
    private static IWindsorContainer _container;

    internal static void Initialize(IWindsorContainer container)
    {
        _container = container;
    }
    internal static TService Resolve<TService>(string key = null)
    {
        if (_container == null)
        {
            throw new InvalidOperationException(
                "ServiceLocator must be initialized with a container by calling Initialize(container).");
        }
        try
        {
            return string.IsNullOrEmpty(key)
                ? _container.Resolve<TService>()
                : _container.Resolve<TService>(key);
        }
        catch (ComponentNotFoundException ex)
        {
            throw new InvalidOperationException(string.Format("No component for {0} has been registered.", typeof(TService).FullName), ex);
        }
    }

    internal static void Release(object resolved)
    {
        _container.Release(resolved);
    }
}

public class ResolvedService<TService> : IDisposable
{
    private bool _disposed;

    private readonly TService _resolvedInstance;

    public TService Service
    {
        get { return _resolvedInstance; }
    }

    public ResolvedService(string key = null)
    {
        _resolvedInstance = ServiceLocator.Resolve<TService>(key);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~ResolvedService()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;
        ServiceLocator.Release(_resolvedInstance);
        _disposed = true;
    }
}

【讨论】:

  • 将一个容器替换为另一个“微不足道”的容器简直是大错特错。挑战在于他们如何释放资源。这是可行的,但现在编写与服务位置混合的与 DI 无关的代码的挑战是丑陋的,需要一些妥协。仍然存在值得的情况。所以我称之为“绝望”。
猜你喜欢
  • 2011-01-18
  • 2015-10-08
  • 2019-06-29
  • 1970-01-01
  • 1970-01-01
  • 2015-12-03
  • 2011-11-03
  • 1970-01-01
  • 2023-03-12
相关资源
最近更新 更多