Как можно использовать существующий экземпляр, чтобы выбрать тип для создания в контейнере IoC - PullRequest
6 голосов
/ 17 июня 2011

это, наверное, просто вопрос новичка, но у меня есть следующее:

public class FooSettings {}
public class BarSettings {}
public class DohSettings {}
// There might be many more settings types...

public interface IProcessor { ... }

public class FooProcessor
    : IProcessor
{
     public FooProcessor(FooSettings) { ... }
}

public class BarProcessor
    : IProcessor
{
     public BarProcessor(BarSettings) { ... }
}

public class DohProcessor
    : IProcessor
{
     public DohProcessor(DohSettings) { ... }
}

// There might be many more processor types with matching settings...

public interface IProcessorConsumer {}

public class ProcessorConsumer 
    : IProcessorConsumer
{
     public ProcessorConsumer(IProcessor processor) { ... }
}

Экземпляр FooSettings или BarSettings предоставляется из внешнего источника, то есть ::1004*.

object settings = GetSettings();

А теперь я хотел бы разрешить ProcessorConsumer на основе внедрения существующего экземпляра настроек, например ::100100

container.RegisterAssemblyTypes(...); // Or similar
container.Inject(settings);
var consumer = container.Resolve<IProcessorConsumer>();

То есть, если предоставляется экземпляр FooSettings, то FooProcessor создается и внедряется в ProcessorConsumer, который затем разрешается экземпляром.

Я не смог понять, как это сделать ни в StructureMap, ни в Ninject, ни в Autofac ... вероятно, потому что я новичок, когда дело доходит до контейнеров IoC. Поэтому ответы на все эти или другие контейнеры, чтобы их можно было сравнить, будут высоко оценены.

ОБНОВЛЕНИЕ: Я ищу решение, которое легко позволяет добавлять новые настройки и процессоры. Также будет однозначное сопоставление типа настроек с типом процессора. Но это также позволяет внедрять другие экземпляры / сервисы в данный тип процессора на основе его параметров конструктора. То есть некоторым процессорам может потребоваться служба IResourceProvider или аналогичная. Просто пример здесь.

В идеале я бы хотел что-то вроде

container.For<IProcessor>.InjectConstructorParameter(settings)

или аналогичный. Таким образом, указав контейнеру IoC использовать тип процессора, соответствующий экземпляру параметра внедренного конструктора.

Ответы [ 7 ]

6 голосов
/ 17 июня 2011

Вы не хотите внедрения зависимости для этого. Вы хотите фабрику (которую, конечно, вы можете построить, используя свой контейнер). Фабрика знает, как взять, скажем, IProcessorSettings и вернуть соответствующий IProcessor. Короче говоря, вы можете построить фабрику, которая использует конкретный тип объекта, который реализует IProcessorSettings, и контейнер для разрешения экземпляра соответствующего типа.

1 голос
/ 24 июня 2011

Я думаю, вы ищете метод ForObject() в StructureMap. Он может закрывать открытый универсальный тип на основе данного экземпляра объекта. Ключевое изменение, которое вам нужно внести в свой дизайн, это ввести универсальный тип:

public interface IProcessor { }
public interface IProcessor<TSettings> : IProcessor{}

Все важные вещи все еще объявлены в IProcessor, универсальный IProcessor<TSettings> - это просто интерфейс маркера. Каждый из ваших процессоров затем реализует универсальный интерфейс, чтобы объявить, какой тип настроек они ожидают:

public class FooProcessor : IProcessor<FooSettings>
{
     public FooProcessor(FooSettings settings) {  }
}

public class BarProcessor : IProcessor<BarSettings>
{
     public BarProcessor(BarSettings settings) {  }
}

public class DohProcessor : IProcessor<DohSettings>
{
     public DohProcessor(DohSettings settings) {  }
}

Теперь, учитывая экземпляр объекта настроек, вы можете получить правильный IProcessor:

IProcessor processor = container.ForObject(settings).
  GetClosedTypeOf(typeof(IProcessor<>)).
  As<IProcessor>();

Теперь вы можете указать StructureMap использовать эту логику всякий раз, когда она разрешает IProcessor:

var container = new Container(x =>
{
    x.Scan(scan =>
    {
        scan.TheCallingAssembly();
        scan.WithDefaultConventions();
        scan.ConnectImplementationsToTypesClosing(typeof(IProcessor<>));
    });

    x.For<IProcessor>().Use(context =>
    {
        // Get the settings object somehow - I'll assume an ISettingsSource
        var settings = context.GetInstance<ISettingsSource>().GetSettings();
        // Need access to full container, since context interface does not expose ForObject
        var me = context.GetInstance<IContainer>();
        // Get the correct IProcessor based on the settings object
        return me.ForObject(settings).
            GetClosedTypeOf(typeof (IProcessor<>)).
            As<IProcessor>();
    });

});
1 голос
/ 22 июня 2011

В Autofac дано:

public class AcceptsTypeConstructorFinder
    : IConstructorFinder
{
    private readonly Type m_typeToAccept;
    public AcceptsTypeConstructorFinder(Type typeToAccept)
    {
        if (typeToAccept == null) { throw new ArgumentNullException("typeToAccept"); }
        m_typeToAccept = typeToAccept;
    }

    public IEnumerable<ConstructorInfo> FindConstructors(Type targetType)
    {
        return targetType.GetConstructors()
            .Where(constructorInfo => constructorInfo.GetParameters()
                .Select(parameterInfo => parameterInfo.ParameterType)
                .Contains(m_typeToAccept));
    }
}

следующие работы:

        // Load
        var settings = new BarSettings();
        var expectedProcessorType = typeof(BarProcessor);

        // Register
        var constructorFinder = new AcceptsTypeConstructorFinder(settings.GetType());
        var builder = new ContainerBuilder();
        var assembly = Assembly.GetExecutingAssembly();

        builder.RegisterInstance(settings);

        builder.RegisterAssemblyTypes(assembly)
               .Where(type => type.IsAssignableTo<IProcessor>() && constructorFinder.FindConstructors(type).Any())
               .As<IProcessor>();

        builder.RegisterAssemblyTypes(assembly)
               .As<IProcessorConsumer>();

        using (var container = builder.Build())
        {
            // Resolve
            var processorConsumer = container.Resolve<IProcessorConsumer>();

            Assert.IsInstanceOfType(processorConsumer, typeof(ProcessorConsumer));
            Assert.IsInstanceOfType(processorConsumer.Processor, expectedProcessorType);

            // Run
            // TODO
        }

Однако я нахожу это довольно громоздким и надеялся на что-то более встроенное в контейнер IoC.

1 голос
/ 19 июня 2011

Контейнеры StructureMap предоставляют свойство Model, которое позволяет запрашивать содержащиеся в нем экземпляры.

var container = new Container(x =>
{
    x.For<IProcessorConsumer>().Use<ProcessorConsumer>();
    x.For<IProcessor>().Use(context =>
    {
        var model = context.GetInstance<IContainer>().Model;
        if (model.PluginTypes.Any(t => typeof(FooSettings).Equals(t.PluginType)))
        {
            return context.GetInstance<FooProcessor>();
        }
        return context.GetInstance<BarProcessor>();
    });
});
0 голосов
/ 27 июня 2011

Я думаю, проблема в том, что вы фактически не указали каким-либо структурированным способом, как разрешить процессор из экземпляра настроек.Как насчет того, чтобы заставить объект настроек вернуть процессор?Кажется, это правильное место для размещения этой информации:

public interface ISettings
{
   IProcessor GetProcessor();
}

Каждая реализация должна разрешать собственную реализацию процессора:

public class FooSettings : ISettings
{
   //this is where you are linking the settings type to its processor type
   public IProcessor GetProcessor() { return new FooProcessor(this); }
}

И любой код, нуждающийся в процессоре, получает его из настроекобъект, на который вы можете ссылаться из конструктора потребителя:

var consumer = new ProcessorConsumer(Settings.GetProcessor());
0 голосов
/ 23 июня 2011

Здесь как можно ближе к подходящему заводскому методу. Но есть некоторые проблемы. Во-первых, вот код; тогда и поговорим.

public class FooSettings
{
    public int FooNumber { get; set; }
    public string FooString { get; set; }
}

public class BarSettings
{
    public int BarNumber { get; set; }
    public string BarString { get; set; }
}


public interface IProcessor
{
    void Process();
}

public class FooProcessor : IProcessor
{
    public FooProcessor(FooSettings settings) { }

    public void Process() { }
}

public class BarProcessor : IProcessor
{
    public BarProcessor(BarSettings settings) { }

    public void Process() { }
}


public interface IProcessorFactory
{
    IProcessor GetProcessor(object settings);
}

public interface IProcessorConsumer { }

public class ProcessorConsumer : IProcessorConsumer
{
    private IProcessorFactory _processorFactory;
    private object _settings;

    public ProcessorConsumer(IProcessorFactory processorFactory, object settings)
    {
        _processorFactory = processorFactory;
        _settings = settings;
    }


    public void MyLogic()
    {
        IProcessor processor = _processorFactory.GetProcessor(_settings);

        processor.Process();
    }
}


public class ExampleProcessorFactory : IProcessorFactory
{
    public IProcessor GetProcessor(object settings)
    {
        IProcessor p = null;

        if (settings is BarSettings)
        {
            p = new BarProcessor(settings as BarSettings);
        }
        else if (settings is FooSettings)
        {
            p = new FooProcessor(settings as FooSettings);
        }

        return p;
    }
}

Так в чем же проблема? Это различные типы настроек, которые вы даете своему заводскому методу. Иногда это FooSettings, а иногда и BarSettings. Позже это может быть xxxSettings. Каждый новый тип будет вызывать перекомпиляцию. Если бы у вас был общий класс настроек, это было бы не так.

Другой вопрос? Ваш потребитель получает заводские настройки и настройки и использует их для получения правильного процессора. Если у вас есть кто-то, кто передает их вашему потребителю, просто вызовите эту сущность, вызовите GetProcessor на Фабрике и передайте полученный IP-обработчик потребителю.

0 голосов
/ 18 июня 2011

Теперь я не говорю, что это правильный способ сделать это.Тем не менее, это может быть другой вариант, если вы используете Autofac.Это предполагает, что вы рады, что делегат при регистрации позвонил GetSettings в тот момент, когда вы пытаетесь решить IProcessorConsumer.Если вы можете сделать это, вы можете сделать то, что вы хотите при регистрации, как показано ниже:

var cb = new ConatainerBuilder();
cb.Register(c => 
{
    var settings = GetSettings();
    if(settings is FooSettings)
        return new FooProcessor((FooSettings)settings);
    else if(settings is BarSettings)
        return new BarProcessor((BarSettings)settings);
    else
       throw new NotImplementedException("Hmmm. Got some new fangled settings.");

}).As<IProcessor>();

//Also need to register IProcessorConsumer

Примечание. Этот код может быть неправильным, поскольку я не могу попробовать его прямо сейчас.

...