我在Unity中使用C#4.7.2和PRISM 6。
现在,在循环中需要SomeClass的实例。 Foreach循环运行,我需要SomeClass的新实例。常见的实现方式类似于MyMethod_CommonImplementation中的代码。
如何正确地在DI模式中实现该代码(在MyMethod中)?当然,我可以注入UnityContainer并使用container.Resolve。但是我相信这将是提供服务,而不是依赖注入。
该示例显示了一个循环进行了五次运行。所以我可以注入SomeClass的五个实例。但这真的是正确的方法吗?
注册工作正常,顺便说一句,正确设置了_exampleName。
public class MyClass
{
IMyInterface _exampleName;
public MyClass(IMyInterface exampleName)
{
_exampleName = exampleName;
}
private void MyMethod()
{
for ( int index = 0 ; index < 5 ; index++ )
{
// at this place I want to "reset" the instance of _exampleName for each index
_exampleName.PropertyName = index
_exampleName.DoSomeImportantWork();
}
}
private void MyMethod_CommonImplementation()
{
for ( int index = 0 ; index < 5 ; index++ )
{
SomeClass exampleClassName = new SomeClass();
exampleClassName.PropertyName = index
exampleClassName.DoSomeImportantWork();
}
}
}
答案 0 :(得分:0)
您的代码有多种设计异味。让我们剖析您的代码。
在您的代码中,请执行以下操作:
_exampleName.PropertyName = index;
_exampleName.DoSomeImportantWork();
此代码显示Temporal Coupling design smell。即使必须在调用PropertyName
之前设置DoSomeImportantWork
,但在结构级别上,该API并不能指示我们存在时间耦合。
除此之外,通过更改IMyInterface
的状态,它使IMyInterface
处于有状态。但是,从消费者的角度来看,通过DI注入的服务应该是无状态的。或如Dependency Injection in .NET, second edition所述:
从概念上讲,只有一个服务 Abstraction 实例。在使用者的整个生命周期中,不必担心 Dependency 的多个实例可能存在的可能性。否则,这会给消费者带来不必要的麻烦,这意味着 Abstraction 并非为他们的利益而设计。 [6.2章]
因此,要解决此问题,最好将运行时值通过抽象方法传递,如下所示:
_exampleName.DoSomeImportantWork(index);
这消除了时间耦合,并消除了消费者知道可能涉及多个实例的需求,甚至可能根本不需要多个实例。
但是如果仍然必须创建多个短期实例,则可以将它们的存在隐藏在代理后面:
public class MyInterfaceProxy : IMyInterface
{
public void DoSomeImportantWork(int index)
{
var instance = new MyInterfaceImpl();
instance.DoSomeImportantWork(index);
}
}
您现在可以注入MyInterfaceImpl
,而不是直接将MyClass
注入MyInterfaceProxy
。
但是请注意,有很多方法可以定义这种代理。如果您将MyInterfaceProxy
纳入Composition Root的一部分,甚至可以安全地将容器注入代理。这不会导致Service Locator anti-pattern,因为服务定位符只是可以存在的outside the Composition Root。
代码中发生的另一件事是:
SomeClass exampleClassName = new SomeClass();
exampleClassName.PropertyName = index
exampleClassName.DoSomeImportantWork();
取决于SomeClass
的功能和实现,该代码可能展示了Control Freak反模式(这是一种特定于DI的方式,表示您违反了Dependency Inversion Principle)。当SomeClass
包含某些不确定行为,或者包含您要在以后的某个时间点模拟,替换或拦截的行为时,就是这种情况。在这种情况下,我们称SomeClass
为易失性依赖关系(请参阅the book的第1.3.2章)。易失性依赖关系应隐藏在抽象背后,并通过构造函数注入进行注入。
根据问题中提供的信息,SomeClass
是否实际上是易失性依赖是无法回答的。但是当它存在时,它应该具有类似于IMyInterface
的代码结构。
答案 1 :(得分:0)
首先,非常感谢您的回答以及为此所做的所有工作。
首先,例子
private void MyMethod_CommonImplementation()
{
for ( int index = 0 ; index < 5 ; index++ )
{
SomeClass exampleClassName = new SomeClass();
exampleClassName.PropertyName = index
exampleClassName.DoSomeImportantWork();
}
}
不是真正的代码的一部分,我用它来阐明我的需求。
在真实代码中, SomeClass 源自服务器接口,这些接口将 SomeClass 的功能分离为多个部分。
与任何DI无关,实现看起来像这样
private void MyMethod_CommonImplementation()
{
for ( int index = 0 ; index < 5 ; index++ )
{
ISomeFunctionality exampleFunc = new SomeClass();
exampleFunc.PropertyName = index
exampleFunc.DoSomeImportantWork();
}
}
确实,人们仍然可以争论,那仍然是时间上的耦合。但是出于稳定性和可维护性的原因,与重载方法相比,我更喜欢这种实现。 DoSomeImportantWork()会验证给定的数据。
在您回答之后,我正在实现工厂模式。所以我的实现现在看起来像这样
public interface IFactory<T>
{
T CreateInstance();
}
public interface ISomeClass
{
int PropertyName { get; set; }
void DoSomeImportantWork();
}
internal SomeClass : ISomeClass, IFactory<ISomeClass>
{
public int PropertyName { get; set; }
public void DoSomeImportantWork()
{
// ...
}
public ISomeClass CreateInstance()
{
return new SomeClass();
}
}
public class MyClass
{
IFactory<ISomeClass> _exampleFactory;
public MyClass(IFactory<ISomeClass> exampleFactory)
{
_exampleFactory = exampleFactory;
MyMethod();
}
private void MyMethod()
{
for ( int index = 0 ; index < 5 ; index++ )
{
ISomeClass exampleName = _exampleFactory.CreateInstance();
exampleName.PropertyName = index;
exampleName.DoSomeImportantWork();
}
}
}
我选择了工厂模式而不是代理模式,因为如果我做对了,我将不得不在第三类中实现代理。
还是,我想知道这是否是正确的方法。