2

我阅读了 Miško Hevery 的指南:编写可测试代码,如果“构造函数完成后对象未完全初始化(注意初始化方法)”,它会发出警告信号。

假设我编写了一个 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();
    }
}

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

谢谢

4

3 回答 3

5

也许我误解了你的问题,但我不认为 Simple Injector 是这里的限制因素。由于构造函数应该做的越少越好,你不应该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);
});

当请求 an 并使用已注册的初始化程序进行初始化时,这将注册RedisWrapper要返回的。此注册可防止对容器的隐藏调用。这提高了性能并提高了容器诊断配置的能力。IRedisWrapperRedisWrapper

于 2013-07-27T10:57:46.643 回答
0

如果您的 RedisWrapperFactory 定义在其他层(它从 DB/文件/某些服务获取数据),则代码将终止依赖注入的目的。您的图层直接依赖于另一个图层。此外,这不再可测试,因为您无法创建用于测试的模拟/伪造对象。显然,您不想在测试中进行真正的数据库操作或 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>();
于 2013-07-27T08:06:42.547 回答
0

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

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

编辑:话虽如此,如果合约是在构造函数被调用后必须以某种特定方式初始化,如果你在不调用的情况下IRedisWrapper使用它会看起来有点奇怪。特别是对于熟悉(很多人)的人,而不是熟悉该特定应用程序的 IOC 设置的人(不是很多人)。当然,如果您要使用工厂,正如 Arghya C 所说,也可以通过接口使用它,否则您实际上并没有实现任何目标,因为您无法选择要注入的工厂。Fooinit()IRedisWrapperIRedisWrapper

于 2013-07-27T08:08:33.287 回答