Замок Виндзор, регистрация неожиданное поведение - PullRequest
5 голосов
/ 15 апреля 2019

Я заметил странное поведение:

public class Bar : IBar
{
...something here
}

public class Foo : IBar
{
...something here
}
container.Register(Component.For<IBar>().ImplementedBy<Bar>());
var test1 = container.Resolve<IBar>(); //returns Bar
container.Register(Component.For<Foo>().ImplementedBy<Foo>());
var test2 = container.Resolve<IBar>(); //returns Foo

Почему test2 - это Foo? Я не зарегистрировал Foo как IBar, я четко зарегистрировал его как реализацию для Foo. Я думаю, что только Bar должен быть разрешен, так как это единственная реализация, переданная для IBar. Проект не мой. Там могут быть некоторые странные настройки, сделанные другими разработчиками.

1 Ответ

4 голосов
/ 15 апреля 2019

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

В этом сценарии модульное тестирование - это простой способ выяснить, ведет себя ли что-то так, как мы думаем, в изоляции.

Этот тест проходит:

[TestMethod]
public void registration_returns_expected_type()
{
    var container = new WindsorContainer();
    container.Register(Component.For<IBar>().ImplementedBy<Bar>());
    var test1 = container.Resolve<IBar>(); //returns Bar
    container.Register(Component.For<Foo>().ImplementedBy<Foo>());
    var test2 = container.Resolve<IBar>();
    Assert.IsInstanceOfType(test2, typeof(Bar));
}

Исходя из этого, я посмотрел бы, что еще регистрирует зависимости. Если вы измените вышеперечисленное так, чтобы Foo и Bar были зарегистрированы как реализации IBar, он запускается и по-прежнему возвращает Bar.

Если мы изменим регистрацию Foo на эту:

container.Register(Component.For<IBar>().ImplementedBy<Foo>().IsDefault());

... тогда IBar разрешается как Foo.

Также может случиться, что в проекте есть несколько инсталляторов (классов, которые реализуют IWindsorInstaller), чтобы сохранить небольшие и управляемые регистрации, но они содержат конфликтующие регистрации. Они исполняются примерно так:

container.Install(FromAssembly.This());

... или с помощью команды, которая запускает установщики из других сборок.

В этом случае регистрация, которая «выигрывает» при запуске, может быть недетерминированной. Меня укусило то, что когда кто-то еще добавил другую регистрацию для того же компонента, мой код все еще работал нормально, но в другой среде был выбран другой компонент.

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

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

container.Register(Component.For<IBar>().ImplementedBy<Bar>().IsDefault());

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

Но есть еще проблема. Ничто не мешает им указывать IsDefault при регистрации своих зависимостей, и тогда вы снова возвращаетесь к исходной точке.

(Вы увидите, что все, что после этого идет по кроличьей норе, предпринимает дополнительные шаги для защиты от того, что может никогда не произойти.)

разумный подход заключается в том, что если вы регистрируете реализацию интерфейса, если вы считаете, что возможна регистрация другой реализации (и, если ее легко обнаружить), вы можете проверить. Если другого нет, зарегистрируйте свой по умолчанию. Тогда следующий человек, увидев ваш дефолт, избежит конфликта с ним. Это вряд ли надежно.

Одной из защит является использование именованных зависимостей самостоятельно, хотя это неудобно, если они вам не нужны. (И основная проблема здесь в том, что не совсем очевидно, нужны они вам или нет, если используются различные установщики.)

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

public class MyInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component.For<NeedsBar>().DependsOn(Dependency.OnComponent<IBar, Foo>()));
    }
}

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

Это оставляет несколько вариантов, каждый из которых имеет недостатки:

  • Помните о потенциальной проблеме и следите за ней. Мне не нравится «быть осторожным» как решение для чего-либо, но после того, как это затронуло меня, я обнаружил, что этого достаточно
  • Запустите интеграционные тесты, которые настраивают ваш контейнер с использованием всех ваших компонентов времени выполнения, а затем проверяют контейнер на наличие дубликатов регистраций, которые являются стандартными или нестандартными. Даже зная потенциал дублирующих регистраций, я никогда не чувствовал необходимости делать это.
  • Надеемся, что если интерфейс настолько общий, что он используется в компонентах приложения, то вы можете либо избежать нескольких реализаций, либо в этом случае осознание того, как он используется, приводит к однозначным регистрациям.В других случаях просто определите отдельные интерфейсы ближе к месту, где они необходимы.
...