我读了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();
}
}
我真的很想听听您对上述解决方案的意见。
由于
答案 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
。