从服务定位器转移到依赖注入

时间:2015-03-06 18:25:59

标签: c# dependency-injection simple-injector service-locator

我准备了基于此的示例应用程序,我想讨论转移到Dependency Injection而不是Service Locator。我是DI的新手,所以请耐心等待我。示例应用程序使用Simple Injector作为DI库编写。 Bootstrapper中的注册按预期工作。我每次需要时都有IMessageBox接口的单例和新的实例ComputationCores。

我读了一些关于DI的文章,所以我知道应该有一些Composition根以及它应该如何工作。但我找到了非常基本的例子而没有真正的单词复杂性。

示例代码:

public class DependencyResolver
{

    public static Func<Type, object> ResolveMe;

    public static T GetInstance<T>() where T : class
    {
        return (T)ResolveMe(typeof (T));
    }
}

public interface IMessageBox
{
    void ShowMessage(string message);
}

public class StandardMessageBox : IMessageBox
{
    public StandardMessageBox()
    {
        Console.WriteLine("StandardMessageBox constructor called...");
    }

    ~StandardMessageBox()
    {
        Console.WriteLine("StandardMessageBox destructor called...");
    }

    public void ShowMessage(string message)
    {
        Console.WriteLine(message);
    }
}

public interface IComputationCoreAlpha
{
    int RunComputation(int myParam);
}

public class SyncComputationCoreAlpha : IComputationCoreAlpha
{
    public SyncComputationCoreAlpha()
    {
        Console.WriteLine("SyncComputationCoreAlpha constructor called...");
    }

    ~SyncComputationCoreAlpha()
    {
        Console.WriteLine("SyncComputationCoreAlpha destructor called...");
    }

    public int RunComputation(int myParam)
    {
        return myParam * myParam;
    }
}

public class AsyncComputationCoreAlpha : IComputationCoreAlpha
{
    public AsyncComputationCoreAlpha()
    {
        Console.WriteLine("AsyncComputationCoreAlpha constructor called...");
    }

    ~AsyncComputationCoreAlpha()
    {
        Console.WriteLine("AsyncComputationCoreAlpha destructor called...");
    }


    public int RunComputation(int myParam)
    {
        return myParam * myParam;
    }
}

public interface IComputationCoreBeta
{
    int RunComputation(int myParam);
}

public class SyncComputationCoreBeta : IComputationCoreBeta
{
    public SyncComputationCoreBeta()
    {
        Console.WriteLine("SyncComputationCoreBeta constructor called...");
    }

    ~SyncComputationCoreBeta()
    {
        Console.WriteLine("SyncComputationCoreBeta destructor called...");
    }


    public int RunComputation(int myParam)
    {
        return myParam * myParam;
    }
}

public class AsyncComputationCoreBeta : IComputationCoreBeta
{
    public AsyncComputationCoreBeta()
    {
        Console.WriteLine("AsyncComputationCoreBeta constructor called...");
    }

    ~AsyncComputationCoreBeta()
    {
        Console.WriteLine("AsyncComputationCoreBeta destructor called...");
    }

    public int RunComputation(int myParam)
    {
        return myParam * myParam;
    }
}

public interface IProjectSubPart
{
    int DoCalculations(int myParam);
}

public class ProjectSubPart1 : IProjectSubPart
{
    public int DoCalculations(int myParam)
    {
        var messageBoxService = DependencyResolver.GetInstance<IMessageBox>();
        messageBoxService.ShowMessage("Hardly working 1...");
        var ccA = DependencyResolver.GetInstance<IComputationCoreAlpha>();
        var ccB = DependencyResolver.GetInstance<IComputationCoreAlpha>();

        return ccA.RunComputation(myParam) + ccB.RunComputation(myParam + 1);
    }
}

public class ProjectSubPart2 : IProjectSubPart
{
    public int DoCalculations(int myParam)
    {
        var messageBoxService = DependencyResolver.GetInstance<IMessageBox>();
        messageBoxService.ShowMessage("Hardly working 2...");
        var ccA = DependencyResolver.GetInstance<IComputationCoreAlpha>();

        return ccA.RunComputation(myParam * 3);
    }
}

public class ProjectSubPartN : IProjectSubPart
{
    public int DoCalculations(int myParam)
    {
        var messageBoxService = DependencyResolver.GetInstance<IMessageBox>();
        messageBoxService.ShowMessage("Hardly working N...");
        return -3;
    }
}

public class ManhattanProject
{
    public void RunProject()
    {
        var messageBoxService = DependencyResolver.GetInstance<IMessageBox>();
        messageBoxService.ShowMessage("Project started...");
        var subPart1 = new ProjectSubPart1();
        var subPart2 = new ProjectSubPart2();
        var subPartN = new ProjectSubPartN();

        var result = subPart1.DoCalculations(1) + subPart2.DoCalculations(2) + subPartN.DoCalculations(3);

        messageBoxService.ShowMessage(string.Format("Project finished with magic result {0}", result));
    }
}

public class Sample
{
    public void Run()
    {
        BootStrapper();

        var mp = DependencyResolver.GetInstance<ManhattanProject>();
        mp.RunProject();

    }

    private void BootStrapper()
    {
        var container = new Container();
        container.RegisterSingle<IMessageBox, StandardMessageBox>();
        container.Register<IComputationCoreAlpha, SyncComputationCoreAlpha>();
        container.Register<IComputationCoreBeta, AsyncComputationCoreBeta>();

        DependencyResolver.ResolveMe = container.GetInstance;
    }

}

在DI中,只允许在Composition根中调用Container.GetInstance(resolve方法),而不是其他地方。应该在构造函数中注入大多数依赖项。

Q1: 如果我要转到DI,我认为ManhattanProject的构造函数看起来像这样: ManhattanProject(IMessageBox mb,IComputationCoreAlpha cca,IComputationCoreBeta ccb)。但这会导致每mb,cca,ccb有一个实例。而不是每次cca的新实例,ccb对我的要求。

Q1A: 我想这可以通过cca,ccb的某种抽象工厂来解决,它可以为每个请求提供新的实例。但那么 - BootStrapper的目的是什么?

Q2: ManhattanProject可以包含更多使用不同coputationCores的ProjectSubParts - 例如42.因此,以这种方式使用构造函数注入(提供计算核心)并且应该使用某种外观是完全不合适的。由于外观应该在构造函数中具有有限的args数量,所以我最终会得到许多嵌套的外观。我想这是错的。

Q3: 我正在使用ProjectSubParts,它允许我做一些工作。所有都继承自IProjectSubPart interace。如果我想为不同的ProjectSubParts注入不同的实现,我该怎么做?我应该为每个ProjectSubpart创建新接口,以允许DI容器解析使用哪个实现?

Q4: 基于提供的示例(和服务定位器模式),我很容易创建IComputationCoreAlpha的实例,它可以在内部创建 每次我需要的时候通过调用DependencyResolver.GetInstance来获得新的和干净的内部对象。此外,我可以完全控制它们,当我完成它们的使用时,我可以调用Dispose。如果在DI概念全图中 将在CompositionRoot中创建如何使用这种用法?

由于

1 个答案:

答案 0 :(得分:3)

  

Q1:如果我要转向DI,我认为是构造函数   ManhattanProject应该看起来像这样:   ManhattanProject(IMessageBox mb,IComputationCoreAlpha cca,   IComputationCoreBeta ccb)。

类应该只依赖于他们需要的服务。所以ManhattanProject不应该依赖于任何计算核心,而应该依赖于IProjectSubPart抽象。

  

Q1a:我想这可以通过某种抽象工厂来解决   对于cca,ccb,它可以为每个请求提供新的实例。但是之后 -   BootStrapper的目的是什么?

bootstrapper / composition root的目的是建立对象图。如果您创建工厂抽象,则需要在某处实现。这个&#39;某处&#39;是你的作文根。工厂实现应该在您的组合根目录中。

除了使用工厂外,更好的方法是注入IEnumerable<IProjectSubPart>。在这种情况下,您的ManhattanProject将如下所示:

public class ManhattanProject
{
    private readonly IMessageBox messageBoxService;
    private readonly IEnumerable<IProjectSubPart> parts;

    public ManhattanProject(IMessageBox messageBoxService, 
        IEnumerable<IProjectSubPart> parts) {
        this.messageBoxService = messageBoxService;
        this.parts = parts;
    }

    public void RunProject() {
        messageBoxService.ShowMessage("Project started...");

        var calculationResults =
            from pair in parts.Select((part, index) => new { part, value = index + 1 })
            select pair.part.DoCalculations(pair.value);

        var result = calculationResults.Sum();

        messageBoxService.ShowMessage(
            string.Format("Project finished with magic result {0}", result));
    }
}

当您依赖IEnumerable<IProjectSubPart>时,每次向系统添加新的ManhattanProject实施时,都可以阻止IProjectSubPart更改。在Simple Injector中,您可以按如下方式注册:

// Simple Injector v3.x
container.RegisterSingleton<IMessageBox, StandardMessageBox>();
container.Register<ManhattanProject>();
container.RegisterCollection<IProjectSubPart>(new[] {
    typeof(ProjectSubPart1),
    typeof(ProjectSubPart2),
    typeof(ProjectSubPartN)
});

// Simple Injector v2.x
container.RegisterSingle<IMessageBox, StandardMessageBox>();
container.Register<ManhattanProject>();
container.RegisterAll<IProjectSubPart>(new[] {
    typeof(ProjectSubPart1),
    typeof(ProjectSubPart2),
    typeof(ProjectSubPartN)
});

一般来说,你甚至可以保护应用程序的其他部分不必知道某个抽象有多个实现,但是在你的情况下隐藏它似乎不可能,因为它是{{1} (当前)负责为每个ManhattanProject提供不同的值。但是,如果可能的话,正确的解决方案是让IProjectSubPart直接依赖ManhattanProject而不是依赖IProjectSubPart,你会让组合根注入一个复合实现,如here所描述的那样包裹IEnumerable<IProjectSubPart>

相同的模式可以应用于所有IEnumerable<IProjectSubPart>实现。例如:

IProjectSubPart

这些public class ProjectSubPart1 : IProjectSubPart { private readonly IMessageBox messageBoxService; private readonly IEnumerable<IComputationCoreAlpha> computers; public ProjectSubPart1(IMessageBox messageBoxService, IEnumerable<IComputationCoreAlpha> computers) { this.messageBoxService = messageBoxService; this.computers = computers; } public int DoCalculations(int myParam) { messageBoxService.ShowMessage("Hardly working 1..."); var calculationResults = from pair in computers.Select((computer, index) => new { computer, index }) select pair.computer.RunComputation(myParam + pair.index); return calculationResults.Sum(); } } 实现可以按如下方式注册为集合:

IComputationCoreAlpha
  

Q2:因为在构造函数中,facade应该具有有限的args数量   我最终会得到很多很多嵌套的外墙。

很难说对此有用。可能我使用LINQ查询的给定实现在你的情况下不起作用,但你的例子太广泛而不是非常具体。最后你可能想要一个自定义抽象,但我还不确定。

  

问题3:我正在使用ProjectSubParts,它允许我做一些工作。   所有都继承自IProjectSubPart接口。如果我想要的话   为不同的ProjectSubParts注入不同的实现   我应该怎么做?我应该为每个人创建新的界面吗?   ProjectSubpart允许DI容器解析哪个   实施使用?

这在很大程度上取决于您的设计。我们应该看一下Liskov Substitution Principle,这基本上说给定抽象的任何子类型都应该以与抽象兼容的方式运行。因此,在您的情况下,如果某个类需要某个container.RegisterCollection<IComputationCoreAlpha>(new[] { typeof(SyncComputationCoreAlpha), typeof(AsyncComputationCoreAlpha) }); 实现,并且不能正确地使用不同的实现,则意味着您正在破坏Liskov替换原则。这意味着这些实现的行为不一样,即使它们可能具有确切的方法签名。在这种情况下,您应该将它们分成多个接口。

如果消费者仍然能够正常运行并且改变实现只是一些方便,那么让他们拥有相同的抽象是可以的。好的例子是IProjectSubPart抽象,ILoggerFileLogger实现。在系统的某些部分,您可能认为通过邮件发送邮件很重要。对于依赖于ILogger的类,无论消息是写入文件,通过邮件发送还是根本不发送,它的功能都相同。

您是否违反了LSK,由您决定。

  第四季度:基于提供的样本(和服务定位器模式)   对我来说很容易创建实例   IComputationCoreAlpha可以在内部创建新的和   通过调用DependencyResolver.GetInstance来清理内部对象   我每次都需要。而且我完全可以控制   当我完成时,他们可以打电话给他们    与他们的用法。如果在DI概念中整个图形将是   在CompositionRoot中创建的将是这种用法   可能的?

我会说DI实际上使这项工作更容易。例如,让我们尝试使用服务定位器实现您想要的内容:

MailLogger

这是一个代理类,能够在调用public class LazyComputationCoreAlphaProxy : IComputationCoreAlpha { public int RunComputation(int myParam) { var heavyWeight = DependencyResolver.GetInstance<IComputationCoreAlpha>(); return heavyWeight.RunComputation(myParam); } } 时懒洋洋地创建实例。但这实际上给我们带来了一个问题。如果我们看看消费者将如何使用它,这一点就变得清晰了:

RunComputation

此处public int DoCalculations(int myParam) { var ccA = DependencyResolver.GetInstance<IComputationCoreAlpha>(); return ccA.RunComputation(myParam); } 方法解析服务定位器中的DoCalculations。这将返回IComputationCoreAlpha实例(因为我们在定位器中注册了该实例)。解决之后,我们会在其上调用LazyComputationCoreAlphaProxy。但在RunComputation内,我们再次解决了RunComputation。我们想要解决IComputationCoreAlpha,因为否则我们的IComputationCoreAlpha需要直接依赖于不同的实现,但这会导致违反依赖性倒置原则,并且可能会导致我们有许多不同的{ {1}}秒。每个实现一个。

但是如果我们尝试在这里解析LazyComputationCoreAlphaProxy,定位器将再次返回LazyComputationCoreAlphaProxy,这最终会导致堆栈溢出异常。

现在让我们来看看依赖注入的外观:

IComputationCoreAlpha

这里我们将一个Func工厂注入LazyComputationCoreAlphaProxy的构造函数中。这允许代理不知道它创建的实际类型,同时仍然允许与以前相同的惰性行为。现在我们将构建对象图的那一部分的责任再次委托给我们的组合根。我们可以按如下方式手动连接:

public class LazyComputationCoreAlphaProxy : IComputationCoreAlpha
{
    private readonly Func<IComputationCoreAlpha> factory;

    public LazyComputationCoreAlphaProxy(Func<IComputationCoreAlpha> factory) {
        this.factory = factory;
    }

    public int RunComputation(int myParam) {    
        var heavyWeight = this.factory.Invoke();
        return heavyWeight.RunComputation(myParam);
    }
}

或者我们可以使用Simple Injector的装饰工具为我们做这件事:

LazyComputationCoreAlphaProxy

通过LazyComputationCoreAlphaProxy(() => new SyncComputationCoreAlpha()) 注册,Simple Injector将自动将container.RegisterCollection<IComputationCoreAlpha>(new[] { typeof(SyncComputationCoreAlpha), typeof(AsyncComputationCoreAlpha) }); container.RegisterDecorator( typeof(IComputationCoreAlpha), typeof(LazyComputationCoreAlphaProxy)); 实现包装在RegisterDecorator装饰器中。开箱即用,Simple Injector了解decotator内部的Func工厂代表,它将确保注入一个创建装饰对象的工厂。

但是因为我们现在正处理装饰者的问题。使用装饰器的依赖注入为我们提供了更多改进代码的可能性。例如,IComputationCoreAlpha中的大部分代码看起来都很相似。它们都具有相同的消息框记录代码:

LazyComputationCoreAlphaProxy

如果你有许多不同的IProjectSubPart,这是很多重复的代码,不仅使实际的实现复杂化,而且还需要维护。什么可以将这些基础设施问题(或跨领域关注点)从这些类中移出,并且只实现一次:在装饰者中:

public class ProjectSubPart1 : IProjectSubPart
{
    private readonly IMessageBox messageBoxService;
    private readonly IEnumerable<IComputationCoreAlpha> computers;

    public ProjectSubPart1(IMessageBox messageBoxService,
        IEnumerable<IComputationCoreAlpha> computers) {
        this.messageBoxService = messageBoxService;
        this.computers = computers;
    }

    public int DoCalculations(int myParam) {
        messageBoxService.ShowMessage("Hardly working 1...");

        // part specific calculation
    }
}

使用此装饰器,您可以将部件简化为以下内容:

IProjectSubPart

请注意public class MessageBoxLoggingProjectSubPart : IProjectSubPart { private readonly IMessageBox messageBoxService; private readonly IProjectSubPart decoratee; public MessageBoxLoggingProjectSubPart(IMessageBox messageBoxService, IProjectSubPart decoratee) { this.messageBoxService = messageBoxService; this.decoratee = decoratee; } public int DoCalculations(int myParam) { messageBoxService.ShowMessage("Hardly working 1..."); return this.decoratee.DoCalculations(myParam); } } 不再需要public class ProjectSubPart1 : IProjectSubPart { private readonly IEnumerable<IComputationCoreAlpha> computers; public ProjectSubPart1(IEnumerable<IComputationCoreAlpha> computers) { this.computers = computers; } public int DoCalculations(int myParam) { var calculationResults = from pair in computers.Select((computer, index) => new { computer, index }) select pair.computer.RunComputation(myParam + pair.index); return calculationResults.Sum(); } } 依赖ProjectSubPart1。这样可以清理实现(并且不要忘记您拥有的其他42个实现)。同样,如果我们手动创建这样的部分,我们将按如下方式进行:

IMessageBox

然而,使用Simple Injector,这变得更加容易:

new MessageBoxLoggingProjectSubPart(new ProjectSubPart1(computers));

现在,只要您想要更改记录事物的方式,您只需要更改container.RegisterCollection<IProjectSubPart>(new[] { typeof(ProjectSubPart1), typeof(ProjectSubPart2), typeof(ProjectSubPartN) }); container.RegisterDecorator( typeof(IProjectSubPart), typeof(MessageBoxLoggingProjectSubPart)); 。例如,您希望在操作完成后记录,或者在抛出异常时。这可以防止您在整个应用程序中进行彻底的更改(正如Open/closed Principle所做的那样)。

对不起这篇长篇帖子我很抱歉。这里有一些注入的土豆:

enter image description here