Existe um padrão para inicializar objects criados por meio de um contêiner DI

Eu estou tentando obter Unity para gerenciar a criação de meus objects e quero ter alguns parâmetros de boot que não são conhecidos até o tempo de execução:

No momento, a única maneira que consegui pensar em como fazer isso é ter um método de boot na interface.

interface IMyIntf { void Initialize(string runTimeParam); string RunTimeParam { get; } } 

Então, para usá-lo (no Unity) eu faria isso:

 var IMyIntf = unityContainer.Resolve(); IMyIntf.Initialize("somevalue"); 

Nesse cenário, runTimeParam param é determinado em tempo de execução com base na input do usuário. O caso trivial aqui simplesmente retorna o valor de runTimeParam mas na realidade o parâmetro será algo como nome do arquivo e o método initialize fará algo com o arquivo.

Isso cria vários problemas, a saber, que o método Initialize está disponível na interface e pode ser chamado várias vezes. Definir um sinalizador na implementação e lançando exceção na chamada repetida para Initialize parece muito desajeitado.

No ponto em que resolvo minha interface, não quero saber nada sobre a implementação do IMyIntf . O que eu quero, no entanto, é saber que essa interface precisa de certos parâmetros de boot únicos. Existe uma maneira de anotar (atributos?) A interface com essas informações e passá-las para a estrutura quando o object é criado?

Edit: descreveu a interface um pouco mais.

    Qualquer lugar em que você precise de um valor de tempo de execução para construir uma dependência específica, o Abstract Factory é a solução.

    Ter methods Initialize nas interfaces cheira a uma abstração gotejante .

    No seu caso, eu diria que você deve modelar a interface IMyIntf em como você precisa usá-la – não como você pretende criar implementações dela. Esse é um detalhe de implementação.

    Assim, a interface deve ser simplesmente:

     public interface IMyIntf { string RunTimeParam { get; } } 

    Agora defina o Abstract Factory:

     public interface IMyIntfFactory { IMyIntf Create(string runTimeParam); } 

    Agora você pode criar uma implementação concreta do IMyIntfFactory que cria instâncias concretas do IMyIntf como esta:

     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; } } } 

    Observe como isso nos permite proteger os invariantes da class usando a palavra-chave readonly . Nenhum smelly Os methods de boot são necessários.

    Uma implementação IMyIntfFactory pode ser tão simples como esta:

     public class MyIntfFactory : IMyIntfFactory { public IMyIntf Create(string runTimeParam) { return new MyIntf(runTimeParam); } } 

    Em todos os seus consumidores, onde você precisa de uma instância do IMyIntf , você simplesmente depende do IMyIntfFactory , solicitando-o através da Injeção do Construtor .

    Qualquer contêiner DI que se IMyIntfFactory será capaz de conectar automaticamente uma instância IMyIntfFactory se você registrá-la corretamente.

    Geralmente, quando você encontra essa situação, você precisa revisitar seu design e determinar se está misturando seus objects de dados / com seus serviços puros. Na maioria dos casos (não em todos), você desejará manter esses dois tipos de objects separados.

    Se você precisar de um parâmetro específico de contexto passado no construtor, uma opção é criar uma fábrica que resolva suas dependencies de serviço por meio do construtor e use seu parâmetro de tempo de execução como um parâmetro do método Create () (ou Gerar ( ), Build () ou o que você nomear seus methods de fábrica).

    Ter setters ou um método Initialize () geralmente é considerado um design ruim, já que você precisa “lembrar” de chamá-los e ter certeza de que eles não abrem muito do estado de sua implementação (por exemplo, -calling inicializar ou o setter?).

    Eu também já vi essa situação algumas vezes em ambientes onde estou criando dinamicamente objects ViewModel baseados em objects Model (muito bem descritos por este outro post do Stackoverflow ).

    Eu gostei de como a extensão Ninject permite que você crie dinamicamente fábricas baseadas em interfaces:

    Bind().ToFactory();

    Não consegui encontrar nenhuma funcionalidade semelhante diretamente no Unity ; então escrevi minha própria extensão para o IUnityContainer, que permite registrar fábricas que criarão novos objects com base em dados de objects existentes, basicamente mapeando de uma hierarquia de tipos para uma hierarquia de tipos diferente: UnityMappingFactory @ GitHub

    Com um objective de simplicidade e legibilidade, acabei com uma extensão que permite que você especifique diretamente os mapeamentos sem declarar classs ou interfaces de fábrica individuais (uma economia de tempo real). Você acabou de adicionar os mapeamentos exatamente onde você registra as classs durante o processo normal de bootstrapping …

     //make sure to register the output... container.RegisterType(); container.RegisterType(); //define the mapping between different class hierarchies... container.RegisterFactory() .AddMap() .AddMap(); 

    Então você apenas declara a interface de fábrica de mapeamento no construtor para CI e usa seu método Create ()

     public ImageWidgetViewModel(IImageWidget widget, IAnotherDependency d) { } public TextWidgetViewModel(ITextWidget widget) { } public ContainerViewModel(object data, IFactory factory) { IList children = new List(); foreach (IWidget w in data.Widgets) children.Add(factory.Create(w)); } 

    Como um bônus adicional, quaisquer dependencies adicionais no construtor das classs mapeadas também serão resolvidas durante a criação do object.

    Obviamente, isso não resolverá todos os problemas, mas tem me servido muito bem até agora, então eu pensei que deveria compartilhá-lo. Há mais documentação no site do projeto no GitHub.

    Eu não posso responder com uma terminologia de Unity específica, mas parece que você está apenas aprendendo sobre injeção de dependência. Se assim for, eu recomendo que você leia o guia do usuário breve, claro e cheio de informações para o Ninject .

    Isso o guiará pelas várias opções que você tem ao usar o DI e como responder pelos problemas específicos que enfrentará ao longo do caminho. No seu caso, você provavelmente deseja usar o contêiner DI para instanciar seus objects e fazer com que esse object obtenha uma referência a cada uma de suas dependencies por meio do construtor.

    O passo a passo também detalha como anotar methods, propriedades e até mesmo parâmetros usando atributos para distingui-los em tempo de execução.

    Mesmo que você não use o Ninject, o passo a passo lhe dará os conceitos e a terminologia da funcionalidade que melhor se adequa ao seu propósito, e você deve ser capaz de mapear esse conhecimento para Unity ou outras estruturas de DI (ou convencê-lo a experimentar Ninject) .

    Eu acho que resolvi isso e parece bastante saudável, então deve ser meio certo :))

    Eu divido o IMyIntf em IMyIntf “getter” e “setter”. Assim:

     interface IMyIntf { string RunTimeParam { get; } } interface IMyIntfSetter { void Initialize(string runTimeParam); IMyIntf MyIntf {get; } } 

    Então a implementação:

     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(); //To retrieve an instance of IMyIntf: //1. create the setter IMyIntfSetter setter = container.Resolve(); //2. Init it setter.Initialize("someparam"); //3. Use the IMyIntf accessor IMyIntf intf = setter.MyIntf; 

    IMyIntfSetter.Initialize() ainda pode ser chamado várias vezes, mas usando bits do paradigma Service Locator , podemos envolvê-lo muito bem, para que o IMyIntfSetter seja quase uma interface interna que seja diferente do IMyIntf .