使用container.RegisterAll <tservice>()时解析组件

时间:2015-05-18 14:33:44

标签: c# dependency-injection simple-injector

我尝试在我的类中注入依赖项时遇到问题。当我被困在这里时,我只是试着去了解更多关于简单注射和DI的信息。

所以这是我的主要方法:

static void Main(string[] args)
{
    var container = new Container();

    // Registrations here
    container.RegisterAll<ISimpleLogger>(typeof(DebugLogger), typeof(ConsoleLogger));

    container.Verify();

    Product p = new Product();
    p.TestLog();

    Console.ReadKey();
}

这是我的其他课程:

public interface ISimpleLogger
{
    void Log(string content);
}

public class DebugLogger : ISimpleLogger
{
    public void Log(string content)
    {
        Debug.WriteLine(content);
    }
}

public class ConsoleLogger : ISimpleLogger
{
    public void Log(string content)
    {
        Console.WriteLine(content);
    }
}

public class Product
{
    public string Name { get; private set; }
    public decimal Price { get; private set; }
    public Product(string name, decimal price)
    {
        Name = name;
        Price = price;
    }

    private readonly ISimpleLogger[] loggers;

    public Product(params ISimpleLogger[] _loggers)
    {
        this.loggers = _loggers;
    }

    public void TestLog()
    {
        foreach (var item in loggers)
        {
            item.Log("testing...");
        }
    }

    public static List<Product> GetSampleProducts()
    {
        return new List<Product>
        {
            new Product { Name="West Side Story", Price = 9.99m },
            new Product { Name="Assassins", Price=14.99m },
            new Product { Name="Frogs", Price=13.99m },
            new Product { Name="Sweeney Todd", Price=10.99m}
        };
    }
}

1)我看不到输出&#34;测试......&#34;任何地方。

2)调试我的应用程序,当调用Product构造函数时,它不会注入依赖项,因此我的数组保留0项。

应该是什么?设置容器时配置错误?

2 个答案:

答案 0 :(得分:2)

使用依赖注入,我们构建组件的对象图。组件是我们的应用程序中包含应用程序行为的类。这种对象图通常由长期服务组成。这些服务本身不应包含任何州。应该使用方法调用将状态(或运行时数据)推送到此对象图中。只要你违反这个基本原则,你就会遇到麻烦。

您的Product班级是一个实体。这是一个包含状态的短期对象。防止您的DI库构建包含状态的对象。这会导致模糊性和可维护性问题。

在您的情况下已经发生这种情况,因为您希望创建一个Product类,您希望它是不可变的。所以这意味着它需要通过构造函数初始化。另一方面,您还希望将其依赖项注入到构造函数中,您将为其构建第二个构造函数。但是你只能调用一个构造函数;所以你的Product要么是没有依赖关系的有效产品,要么是带有依赖关系的无效产品。

虽然您可以通过将两个构造函数合并在一起来解决这个问题,但这会导致相同的麻烦,因为现在您将不得不要求DI容器为您构建此产品,但DI容器一无所知它必须提供的原语(在你的情况下是名称和价格)。

在我的项目中,我通常使用anemic domain model。这意味着我的实体在其中没有任何逻辑。相反,我使用commandsqueries作为我的域模型动词的定义。其他人不喜欢这样,并且喜欢将域逻辑作为域对象的一部分(虽然这两个模型不是互斥的,但是一些开发人员结合使用这些概念)。由于域逻辑有时需要使用服务,因此这些实体显然需要提供这些依赖性。但这并不意味着你不得不在这里使用构造函数注入。

在这种情况下,方法注入更方便。这意味着您的Product课程将如下所示:

public class Product
{
    public string Name { get; private set; }
    public decimal Price { get; private set; }
    public Product(string name, decimal price) {
        Name = name;
        Price = price;
    }

    public void TestLog(ISimpleLogger[] loggers) {
        foreach (var item in loggers)
        {
            item.Log("testing...");
        }
    }
}

因此,我们不是通过构造函数传递依赖项,而是通过实际需要它的方法传递它。方法注入效果更好,因为:

  • 您的构造函数冲突消失了。您现在可以使用构造函数创建有效实例,并在需要时提供所需的服务。
  • 它将阻止您的构造函数变大,具有许多依赖项。这将发生,因为虽然您的实体上的大多数方法只需要一个或两个不同的服务,但实体上的所有方法可能需要十几个或更多依赖项。这真的非常难看。您经常会发现域对象上的方法之间没有那么多的内聚,这就是为什么我将这个逻辑移动到我的命令处理程序中(但这是一个不同的故事)。

当然,注入这些依赖项的责任现在转移到调用此域方法的人(在您的情况下为TestLog方法)。但这通常不是问题,因为此消费者将是一个普通的应用程序组件,您可以再次应用构造函数注入。这是一个例子:

public class LogProductCommandHandler : ICommandHandler<LogProduct>
{
    private readonly IRepository<Product> productRespository;
    private readonly ISimpleLogger[] loggers;
    public LogProductCommandHandler(IRepository<Product> productRespository,
        ISimpleLogger[] loggers) {
        this.productRespository = productRespository;
        this.loggers = loggers;
    }

    public void Handle(LogProduct command) {
        Product product = this.productRepository.GetById(command.ProductId);
        product.TestLog(this.loggers);
    }
}

容器可以解析此LogProductCommandHandler。请注意所有运行时数据&#39;流量&#39;通过这里的对象图。我们有LogProduct消息(命令),其中包含运行时数据。它被传递给Handle方法。此消息包含ProductId值,并将其传递到存储库的GetById方法,该方法将返回Product(再次运行时数据)。

但还有一件事。防止向消费者注入事物清单。注入T[]数组,IEnumerable<T>List<T>通常意味着您正在泄漏实施细节。消费者不应该知道或担心在你的情况下可能有多个实现 - 记录器。注入集合会使消费者复杂化,因为它必须迭代集合。不仅如此,依赖该集合的每个消费者都需要迭代这个集合。这不仅使所有这些消费者的代码变得不必要地复杂化,它将使您的代码更难以改变。在某个时间点,您会想要改变处理这些记录器的方式。也许您希望继续记录到下一个记录器,即使第一个记录器引发了异常。我确定你知道如何编程这样的东西;解决这个问题并不困难。但它会让你通过你的代码库进行彻底的改变来实现这一目标。

相反,请使用Composite design pattern。使用复合模式,您可以在实现相同抽象的组件后面隐藏一些抽象元素的集合。 ISimpleLogger的复合实现可能如下所示:

public sealed SimpleLoggerComposite : ISimpleLogger
{
    private readonly IEnumerable<ISimpleLogger> loggers;
    public SimpleLoggerComposite(IEnumerable<ISimpleLogger> loggers) {
        this.loggers = loggers;
    }
    public void Log(string message) {
        foreach (var logger in this.loggers) {
            logger.Log(message);
        } 
    }
}

我们将foreach循环移至SimpleLoggerComposite。现在我们可以按如下方式注册SimpleLoggerComposite

// Simple Injector v3.x
container.RegisterSingleton<ISimpleLogger, SimpleLoggerComposite>();
container.RegisterCollection<ISimpleLogger>(
    new[] { typeof(DebugLogger), typeof(ConsoleLogger) });

// Simple Injector v2.x
container.RegisterSingle<ISimpleLogger, SimpleLoggerComposite>();
container.RegisterAll<ISimpleLogger>(
    new[] { typeof(DebugLogger), typeof(ConsoleLogger) });

现在,应用程序的其余部分可以完全依赖于ISimpleLogger而不是ISimpleLogger[]。例如:

public class Product
{
    ...

    public void TestLog(ISimpleLogger logger) {
        logger.Log("testing...");
    }
}

现在,TestLog方法中的代码变得非常无聊。但这显然是一件好事。软件开发的诀窍是使看起来很简单的软件: - )

答案 1 :(得分:2)

问题非常简单,您似乎已正确配置container,但您没有要求它创建Product对象,因此能够注入依赖项。

而不是new自己Product,请container为你做这件事......

static void Main(string[] args)
{
    var container = new Container();

    // Registrations here
    container.RegisterAll<ISimpleLogger>(typeof(DebugLogger), typeof(ConsoleLogger));

    container.Verify();

    Product p = container.GetInstance<Product>();
    p.TestLog();

    Console.ReadKey();
}

我认为您还需要更改Product的构造函数以取IEnumerable<>

private readonly IEnumerable<ISimpleLogger> loggers;

public Product(IEnumerable<ISimpleLogger> loggers)
{
    this.loggers = loggers;
}