依赖注入和初始化方法

时间:2013-07-27 07:48:09

标签: c# unit-testing design-patterns dependency-injection simple-injector

我读了Miško Hevery's Guide: Writing Testable Code并且如果“在构造函数完成后没有完全初始化对象(注意初始化方法)”,它会发出警告信号。

假设我写了一个Redis包装类,它有一个接受主机名和端口的init方法。根据Miško的说法,这是一个警示标志,因为我需要调用它的init方法。

我正在考虑的解决方案如下: 对于需要这种初始化的每个类,创建一个工厂类,该类具有创建类的Create方法,并且还调用其init方法。

现在代码:而不是使用类似的东西:

class Foo
{
    private IRedisWrapper _redis;
    public Foo(IRedisWrapper redis)
    {
       _redis = redis;
    }
}
....
IRedisWrapper redis = new RedisWrapper();
redis.init("localhost", 1234);
Foo foo = new Foo(redis);

我会使用类似的东西:

class Foo
{
    private IRedisWrapper _redis;
    public Foo(IRedisWrapper redis)
    {
       _redis = redis;
    }
}
....
RedisWrapperFactory redisFactory = new RedisWrapperFactory();
IRedisWrapper redisWrapper = redisFactory.Create();
Foo foo = new Foo(redisWrapper);

我正在使用Simple Injector作为IOC框架,这使得上述解决方案成为问题 - 在这种情况下我会使用类似的东西:

class Foo
{
    private RedisWrapper _redis;
    public Foo(IRedisWrapperFactory redisFactory)
    {
       _redis = redisFactory.Create();
    }
}

我真的很想听听您对上述解决方案的意见。

由于

3 个答案:

答案 0 :(得分:3)

也许我误解了你的问题,但我不认为Simple Injector是一个限制因素。由于构造函数应该以little as possible为单位,因此不应在构造函数中调用Create方法。这甚至是一件奇怪的事情,因为工厂意味着延迟创建一个类型,但是因为你在构造函数中调用Create,所以创建不会延迟。

您的Foo构造函数应该只依赖于IRedisWrapper,您应该像这样提取redisFactory.Create()对DI配置的调用:

var redisFactory = new RedisWrapperFactory();

container.Register<IRedisWrapper>(() => redisFactory.Create());

但由于工厂的唯一目的是在整个应用程序中防止重复的初始化逻辑,它现在已经失去了它的目的,因为工厂使用的唯一地方是DI配置。所以我们可以把工厂扔出去并写下以下注册:

container.Register<IRedisWrapper>(() =>
{
    IRedisWrapper redis = new RedisWrapper();
    redis.init("localhost", 1234);
    return redis;
});

我们现在将Create方法的主体放在匿名委托中。你的RedisWrapper类目前有一个默认的构造函数,所以这种方法很好。但是如果RedisWrapper开始获得自己的依赖关系,那么最好让容器创建该实例。这可以按如下方式完成:

container.Register<IRedisWrapper>(() =>
{
    var redis = container.GetInstance<RedisWrapper>();
    redis.init("localhost", 1234);
    return redis;
});

当您需要在创建后初始化您的类时,正如RedisWrapper显然需要的那样,建议的方法是使用RegisterInitializer方法。最后一段代码片段可以写成如下:

container.Register<IRedisWrapper, RedisWrapper>();

container.RegisterInitializer<RedisWrapper>(redis =>
{
    redis.init("localhost", 1234);
});

这会在请求RedisWrapper时注册要返回的IRedisWrapper,并使用注册的初始化程序初始化RedisWrapper。此注册可防止隐藏的回调容器。这样可以提高性能并提高容器diagnose your configuration的能力。

答案 1 :(得分:0)

如果您的RedisWrapperFactory在某个其他层(从DB / File /某些服务获取数据)中定义,则该代码将终止可靠性注入的目的。您的图层将直接依赖于另一个图层。此外,由于您无法创建用于测试的模拟/虚假对象,因此不再可测试。显然,您不希望在测试中进行真正的数据库操作或I / O读写或服务调用。

您可能想要做类似......

的事情
class Foo
{
    private IRedisWrapper _redis;

    public Foo(IRedisWrapperFactory redisFactory)
    {
        _redis = redisFactory.Create();
    }
}

在另一层

public class RedisWrapperFactory : IRedisWrapperFactory
{
    public IRedisWrapper Create()
    {
        var r = RedisWrapper();
        r.Init("localhost", 1234); //values coming from elsewhere
        return r;
    }                                   
}

在你的Bootstrapper()或application_Start()方法中,注入工厂,如

container.Register<IRedisWrapperFactory, RedisWrapperFactory>();

答案 2 :(得分:0)

RedisWrapperFactory作为依赖项似乎不太正确,因为它并不是您想要的工厂。当然,除非您需要将特定参数传递给Create()

我不知道Simple Injector,但我建议如果它不允许您自定义对象的创建以使用您的工厂,您可能需要查看其他一些DI框架。我使用StructureMap,但还有其他人可以选择。

编辑:话虽如此,如果IRedisWrapper的合同是在调用构造函数后必须以某种特定的方式初始化,如果你使用它会看起来有点奇怪它在Foo中没有调用init()。特别是对熟悉IRedisWrapper(很多人)的人,而不是那个特定应用程序的IOC设置(不是很多人)。当然,如果你打算使用工厂,正如Arghya C所说的那样,也可以通过界面使用它,否则你实际上没有取得任何成就,因为你无法选择注射的IRedisWrapper