DI 컨테이너를 통해 생성 된 객체를 초기화하는 패턴이 있습니까?
Unity가 객체 생성을 관리하도록하고 있는데 런타임까지 알려지지 않은 초기화 매개 변수를 갖고 싶습니다.
현재 내가 생각할 수있는 유일한 방법은 인터페이스에 Init 메소드를 사용하는 것입니다.
interface IMyIntf {
void Initialize(string runTimeParam);
string RunTimeParam { get; }
}
그런 다음 Unity에서 사용하려면 다음과 같이하십시오.
var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");
이 시나리오에서 runTimeParam
매개 변수는 사용자 입력에 따라 런타임에 결정됩니다. 여기서 사소한 경우는 단순히 값을 반환 runTimeParam
하지만 실제로 매개 변수는 파일 이름과 같으며 initialize 메소드는 파일과 함께 작동합니다.
이로 인해 여러 가지 문제가 발생합니다. 즉 Initialize
, 인터페이스 에서 메소드를 사용할 수 있으며 여러 번 호출 할 수 있습니다. 구현에서 플래그를 설정하고 반복 호출에 대한 예외를 던지는 Initialize
것은 어색한 것처럼 보입니다.
인터페이스를 해결하는 시점에서의 구현에 대해 알고 싶지 않습니다 IMyIntf
. 그러나 내가 원하는 것은이 인터페이스에 특정 시간 초기화 매개 변수가 필요하다는 것입니다. 이 정보로 인터페이스에 주석을 달고 (속성?) 객체를 만들 때 프레임 워크에 전달하는 방법이 있습니까?
편집 : 인터페이스를 조금 더 설명했습니다.
특정 의존성을 구성하기 위해 런타임 값이 필요한 곳이면 Abstract Factory 가 솔루션입니다.
인터페이스에서 메소드를 초기화하면 새는 추상화 냄새가납니다 .
귀하의 경우 IMyIntf
인터페이스를 구현하려는 의도가 아니라 사용 방법 에 대한 인터페이스를 모델링해야한다고 말하고 싶습니다 . 그것은 구현 세부 사항입니다.
따라서 인터페이스는 다음과 같아야합니다.
public interface IMyIntf
{
string RunTimeParam { get; }
}
이제 추상 팩토리를 정의하십시오.
public interface IMyIntfFactory
{
IMyIntf Create(string runTimeParam);
}
이제 다음 과 같은 IMyIntfFactory
구체적인 인스턴스를 만드는 구체적인 구현을 만들 수 있습니다 IMyIntf
.
public class MyIntf : IMyIntf
{
private readonly string runTimeParam;
public MyIntf(string runTimeParam)
{
if(runTimeParam == null)
{
throw new ArgumentNullException("runTimeParam");
}
this.runTimeParam = runTimeParam;
}
public string RunTimeParam
{
get { return this.runTimeParam; }
}
}
이것이 키워드 를 사용 하여 클래스의 불변 을 보호 하는 방법에 주목하십시오 readonly
. 냄새가 나지 않습니다. 초기화 방법이 필요합니다.
IMyIntfFactory
구현이 단순하게 할 수있다 :
public class MyIntfFactory : IMyIntfFactory
{
public IMyIntf Create(string runTimeParam)
{
return new MyIntf(runTimeParam);
}
}
In all your consumers where you need an IMyIntf
instance, you simply take a dependency on IMyIntfFactory
by requesting it through Constructor Injection.
Any DI Container worth its salt will be able to auto-wire an IMyIntfFactory
instance for you if you register it correctly.
Usually when you encounter this situation, you need to revisit your design and determine if you are mixing your stateful/data objects with your pure services. In most (not all) cases, you will want to keep these two types of objects separate.
If you do need a context-specific parameter passed in the constructor, one option is to create a factory that resolves your service dependencies via the constructor, and takes your run-time parameter as a parameter of the Create() method (or Generate(), Build() or whatever you name your factory methods).
Having setters or an Initialize() method are generally thought to be bad design, as you need to "remember" to call them and make sure they don't open up too much of your implementation's state (i.e. what is to stop someone from re-calling initialize or the setter?).
I also have come across this situation a few times in environments where I am dynamically creating ViewModel objects based on Model objects (outlined really well by this other Stackoverflow post).
I liked how the Ninject extension which allows you to dynamically create factories based on interfaces:
Bind<IMyFactory>().ToFactory();
I could not find any similar functionality directly in Unity; so I wrote my own extension to the IUnityContainer which allows you to register factories that will create new objects based on data from existing objects essentially mapping from one type hierarchy to a different type hierarchy: UnityMappingFactory@GitHub
With a goal of simplicity and readability, I ended up with an extension that allows you to directly specify the mappings without declaring individual factory classes or interfaces (a real time saver). You just add the mappings right where you register the classes during the normal bootstrapping process...
//make sure to register the output...
container.RegisterType<IImageWidgetViewModel, ImageWidgetViewModel>();
container.RegisterType<ITextWidgetViewModel, TextWidgetViewModel>();
//define the mapping between different class hierarchies...
container.RegisterFactory<IWidget, IWidgetViewModel>()
.AddMap<IImageWidget, IImageWidgetViewModel>()
.AddMap<ITextWidget, ITextWidgetViewModel>();
Then you just declare the mapping factory interface in the constructor for CI and use its Create() method...
public ImageWidgetViewModel(IImageWidget widget, IAnotherDependency d) { }
public TextWidgetViewModel(ITextWidget widget) { }
public ContainerViewModel(object data, IFactory<IWidget, IWidgetViewModel> factory)
{
IList<IWidgetViewModel> children = new List<IWidgetViewModel>();
foreach (IWidget w in data.Widgets)
children.Add(factory.Create(w));
}
As an added bonus, any additional dependencies in the constructor of the mapped classes will also get resolved during object creation.
Obviously, this won't solve every problem but it has served me pretty well so far so I thought I should share it. There is more documentation on the project's site on GitHub.
I can't answer with specific Unity terminology but it sounds like you are just learning about dependency injection. If so, I urge you to read the brief, clear, and information packed user guide for Ninject.
This will walk you through the various option you have when using DI, and how to account for the specific issues that you'll face along the way. In your case, you would most likely want to use the DI container to instantiate your objects, and have that object get a reference to each of its dependencies through the constructor.
The walkthrough also details how to annotate methods, properties, and even parameters using attributes to distinguish them at runtime.
Even if you do not use Ninject, the walkthrough will give you the concepts and terminology of the functionality that suits your purpose, and you should be able to map that knowledge to Unity or other DI frameworks (or convince yout to give Ninject a try).
I think I solved it and it feels rather wholesome, so it must be half right :))
I split IMyIntf
into a "getter" and a "setter" interfaces. So:
interface IMyIntf {
string RunTimeParam { get; }
}
interface IMyIntfSetter {
void Initialize(string runTimeParam);
IMyIntf MyIntf {get; }
}
Then the implementation:
class MyIntfImpl : IMyIntf, IMyIntfSetter {
string _runTimeParam;
void Initialize(string runTimeParam) {
_runTimeParam = runTimeParam;
}
string RunTimeParam { get; }
IMyIntf MyIntf {get {return this;} }
}
//Unity configuration:
//Only the setter is mapped to the implementation.
container.RegisterType<IMyIntfSetter, MyIntfImpl>();
//To retrieve an instance of IMyIntf:
//1. create the setter
IMyIntfSetter setter = container.Resolve<IMyIntfSetter>();
//2. Init it
setter.Initialize("someparam");
//3. Use the IMyIntf accessor
IMyIntf intf = setter.MyIntf;
IMyIntfSetter.Initialize()
can still be called multiple times but using bits of Service Locator paradigm we can wrap it up quite nicely so that IMyIntfSetter
is almost an internal interface that is distinct from IMyIntf
.
'Programming' 카테고리의 다른 글
ResponseEntity를 사용하는 경우 (0) | 2020.06.15 |
---|---|
작업 또는 작업 세트별로 사용자를 전환하는 방법은 무엇입니까? (0) | 2020.06.15 |
Node.js에서 process.env.PORT은 (는) 무엇 이죠? (0) | 2020.06.15 |
List 유형의 속성을 유지하는 방법 (0) | 2020.06.15 |
루비 수면 또는 1 초 미만 지연? (0) | 2020.06.15 |