Как получить привязку на основе фабричного параметра -> путь к пространству имен?

Я использую Ninject 3.0.1.10 и ninject.extensions.factory 3.0.1.0 из NuGet - в "реальном" сценарии я также буду использовать ninject.extensions.conventions (вместо того, чтобы вручную связывать IFoo), но я хотел держитесь подальше от этого, чтобы попытаться упростить вопрос.

У меня есть интерфейс IFoo и несколько его реализаций, каждая из которых находится в дочернем пространстве имен и подпапках с именами Gen1 и Gen2. У меня есть интерфейс IFooFactory, где предполагается, что он возвращает IFoo на основе указанного параметра (строка, перечисление, что угодно).

Я использую перечисление в этом примере просто для того, чтобы попытаться сделать его более понятным - сначала я сделал строковую версию, но чувствовал, что возражения по поводу передачи более произвольного параметра, такого как строка, просто запутают проблему.

public enum ImplementationGeneration
{
    Gen1,
    Gen2,
    Gen3,
}

public interface IFoo
{
    void DoStuff();
}

public interface IFooFactory
{
    IFoo CreateFoo(ImplementationGeneration implementationGeneration);
}


namespace SomeRootNamespace.Gen1
{
    public class Foo : IFoo
    {
        public void DoStuff()
        {
            Console.WriteLine("Doing Type1 stuff");
        }
    }
}

namespace SomeRootNamespace.Gen2
{
    public class Foo : IFoo
    {
        public void DoStuff()
        {
            Console.WriteLine("Doing Type2 stuff");
        }
    }
}

Теперь я понимаю, что если потребитель "выберет" реализацию, подобную этой, это форма соединения, которая в идеале не существовала бы, но ИМХО, это тот же уровень связи, что и у именованных привязок, который Ninject уже поддерживает. Я хотел избежать добавления атрибутов к реализациям, и наличие методов GetGen1 / GetGen2 / etc в интерфейсе фабрики является болезненным приспособлением для этого, так как я бы в конечном итоге нарушил OCP через переключатель где-то для сопоставления ввода с методом для вызова (или вручную, используя отражение)

Полный / рабочий код, который у меня есть сейчас, который я бы предпочел избежать, если это возможно, находится здесь: https://gist.github.com/4549677

Он использует 2 подхода:

  1. ручная реализация фабрики, которая нарушает OCP с переключателем на переданном перечислении
  2. использование фабричного расширения с экземпляром IInstanceProvider (подклассы StandardInstanceProvider для переопределения GetInstance).

Второй подход выглядит так, как будто он может быть "близок" к "правильному" способу заставить это работать, но 1) он использует ссылку на ядро, чтобы выполнить свою работу, что, вероятно, является плохой идеей, и 2), поскольку Я не смог найти конкретный тип в привязках для IFoo при поиске по всем привязкам IFoo во время вызова, в настоящее время он выполняет GetAll, поэтому он создает на N-1 больше экземпляров, чем необходимо для этого сценария.

1 ответ

Решение

Ну, я нашел что-то, по крайней мере, лучше, чем то, что у меня было выше.

В конце концов, он использует именованные привязки, а https://github.com/ninject/ninject.extensions.conventions используется для именования всех привязок на основании последней части их пространства имен. Это приводит к тому, что имена присоединяются к большому количеству привязок, которым это не нужно (с единственной реализацией, доступной для данного интерфейса), хотя присоединение имен к этим привязкам не вызывает проблем при их использовании (по крайней мере, в моем тестировании).

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

kernel.Bind(x => x
    .FromThisAssembly()
    .SelectAllClasses()
    .BindAllInterfaces()
    .Configure((binding, concreteType) =>
    {
        var concreteNamespace = concreteType.Namespace ?? String.Empty;
        var lastNamespacePart = concreteNamespace.Split('.').Last();
        binding.Named(lastNamespacePart);
    })
);

Затем он использует UseFirstArgumentAsNameInstanceProvider для поиска имени привязки на основе первого параметра метода фабрики (поэтому вам не нужно иметь отдельные методы для GetGen1, GetGen2 и т. Д.). Я просто изменил переопределение GetName, чтобы сделать ToString, так как я передавал перечисление вместо фактической строки, но в остальном это то же самое, что и со связанной вики-страницы.

public class UseFirstArgumentAsNameInstanceProvider : StandardInstanceProvider
{
    protected override string GetName(System.Reflection.MethodInfo methodInfo, object[] arguments)
    {
        return arguments[0].ToString();
    }

    protected override ConstructorArgument[] GetConstructorArguments(System.Reflection.MethodInfo methodInfo, object[] arguments)
    {
        return base.GetConstructorArguments(methodInfo, arguments).Skip(1).ToArray();
    }
}

Я собираюсь оставить вопрос открытым на некоторое время, на случай, если будет доступен другой / лучший вариант, но, по крайней мере, это кажется разумным и не имеет никаких явных проблем с OCP.:)

Другие вопросы по тегам