Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Это оправдано по производительности, т.к. разрешение зависимостей делается единожды, а параметры передаются для каждой уникальной пары из ранжировщика и команды.
Это оправдано архитектурно, т.к. компоненты использующие DD могут использовать друг друга, а, следовательно, ранжировщики смогут получить доступ не только к данным, зарегистрированным в текушем контексте, но и к данным зарегистрированным во внешнем контексте по отношению к TopBuilder.
при кривой конфигурации
А это (доступ ко внешним данным) кому-то нужно? А вот прозрачность системы вы резко понизили, что на архитектуре сказывается отрицательно.
public interface IRankerProvider
{
IEnumerable<IRanker> GetRankers();
}
public interface IRanker
{
int Rank(ITeam team);
}
public class RangerComposition : IRanker
{
private IRanger[] rangers;
public RangerComposition(IRanger[] rangers)
{
this.rangers = rangers;
}
pubic int Rank(ITeam team)
{
// Только для примера
return _rangers.First().Rank(team);
}
}
container.RegisterType<IModule, ModuleCompiosition>(); //default
container.RegisterType<IModule, One>("One");
container.RegisterType<IModule, Two>("Two");
container.RegisterType<IModule, Three>("Two");
container.Resolve<IModule>(); // без указания имени регистрации
В зависимости от используемого контейнера, может дополнительно понадобиться примитив поставщика оценщиковУпомянутая возможность Unity мне хорошо известна и была использована в первую очередь. Проблема может возникнуть, если оценщики зависят друг от друга, именованые регистрации в Unity, к сожалению, друг о друге ничего не знают. Дефолтная реализация
IRankerProvider делает именно то, что вы написали — разрешает массив. Но, сам интерфейс делается для того, чтобы поведеление можно было изменить. Можно ещё зарегистрировать InjectionFactory для массива. Как говорится, по желанию и никакого приведения типов.BuilerWorker в даной ситуации больше похож на паттерн «MethodObject» и ничто не мешает его тестировать, при соответствующем изменении видимости.Моим примером я избавила вас как минимум от двух не нужных компонентов:
закрытого и не тестируемого BuilerWorker а так же от IRankerProvider
BuildTop. Применяя шаблон Data Dependency мы позволяем любой реализации ранжировщика получить доступ к этим данным, но не обязываем её это делать.IRanker не потребуется. Таким образом, мы удовлетворим новое требование и сохраним совместимость с имеющимися реализациями.new с передачей параметра» — это как раз DI и есть.IRanker чтобы выставить оценку. Это не имеет для алгоритма ранжирования никакого значения. Зачем же я должен передавать в качестве параместра всё что может понадобиться какой-то из реализацй, но скорее всего не понадобится?С точки зрения абстракции мне удобнее не знать, какие именно данные нужны конкретному IRanker чтобы выставить оценку.
Это не имеет для алгоритма ранжирования никакого значения.
Зачем же я должен передавать в качестве параместра всё что может понадобиться какой-то из реализацй, но скорее всего не понадобится?
Алгоритм конкретного IRanker зависит от этого.
Вы все равно это передаете. Просто не в вызов, а в конструктор. Суть от этого не меняется.
не передаю. Я позволяю любой конкретной реализации получить эти данные, если ей это нужно. Происходит инверсия управления.Я данные не передаю. Я позволяю любой конкретной реализации получить эти данные, если ей это нужно. Происходит инверсия управления.
IRanker, не думать более об этом при каждом вызове.Но сразу после того как я инжектирую данные в контейнер я могу забыть об этом и, при использовании любого конктерного сконструированного IRanker, не думать более об этом при каждом вызове.
Вот только вам надо не забыть вбросить данные в контейнер (не имея никакой конкретной информации о том, какие именно данные нужны потребителям)
при этом не забыть породить все потребители именно в этом контейнере.
По сути, вы используете дочерний контейнер как контекст-бэг.
Поэтому вбрасываются все данные которые есть.
Благо список всего, что им можно получить четко обозначен контрактом.
Т.к. компонент в этом случае не заработает в принципе, даже если данные не нужны никому.
И не пройдет ни один юнит тест.
Да. Одновременно с этим ограничивая время жизни зависящих от контекста компонентов временем жизни дочернего контейнера (не больше), что позволяет использовать контексто-зависимые техники кэширования и т.п.
Погода на Марсе — тоже?
BuildTopКаким контрактом?
Юнит-тесты тут вообще не при чем, корректность вбрасывания проверяется интеграционными.
По факту, это означает, что вы используете обычный контекст, просто извращенным способом.
Смысл называть это отдельным шаблоном?
Нет. Только то что передано в качастве параметров методоу BuildTop
Описанием метода BuildTop
Я говорю про тесты на способность реализации метода BuildTop выполнять его функцию.
Ну то есть вы все равно где-то явно определили этот набор данных. Причем в потребителе зависимости (т.е., том месте, о котором зависимость знать не должна).
BuildTop) помечаеются аттрибутами говорящими «Я инжектирую это, это и это».IRanker) помечаются аттрибутом «Я ожидаю данные этого, этого и этого типов из контейнера».IRanker не нужно знать кто эти данные инжектирует, но это всегда можно узнать по парным аттрибутам.Интерфейсы компонентов, реализация которых подразумевает разрешение данных из контейнера (IRanker) помечаются аттрибутом «Я ожидаю данные этого, этого и этого типов из контейнера».
Ну и да, какой с этих атрибутов толк? У вас есть статическая проверка того, что при любой активации зависимости эти данные вброшены в контекст
А вообще — двусторонняя связанность, потребитель знает о зависимости, а зависимость знает о том, что потребитель в нее вбросит. Как раз то, чего IoC пытается избежать.
ITopBuilder и IRanker. К таковым может относиться композит из всех реализаций IRanker.Статической быть не может,
динамическую делает сам контейнер.
Но передача контекста параметром с этой точки зрения не лучше.
Лучше. В этом случае знание односторонне — все знают о контексте, контекст не знает ни о ком
Собственно, возвращаясь к вопросу: а смысл атрибутов тогда?
А ещё все знают о том, что все знают о контексте, и что его надо всем передавать…
Обычно для решения вашей проблемы банально вводят контекст операции.
Unity помимо прочего гарантирует освобождение ресурсов и время жизни объектов в контейнере
При вызове Dispose у контейнера освобождается всё что в контейнере заргистрировано.
Аттрибутами на интерфейсе, на на которые есть четкая документация.
проверять наличие в теле метода вызова Register
Например, подготовку контейнера, в который кладётся 100500 данных (включая погоду на марсе), я бы вынес в отдельную функцию.
BuildTop сама ничего помимо этого не делает. Сама логика вынесена в Method Object. В контейнер кладется не всё (включая погоду на марсе), а только данные в контексте. Этот набор не может быть шире набора параметров метода. Собственно на параметры метода интерфейса и навешиваются аттрибуты.А ещё можно полунастроенные контейнеры передавать в функции, чтобы повторно не настраивать для множества вызовов с одинаковыми доп. параметрами. В таких сценариях метод, вызывающий сервис, не будет содержать в своём теле вызовы RegisterInstance.
int GetAverageRank()
{
var childcontainer = _container.CreateChildContainer();
childcontainer.RegisterInstance(teams);
childcontainer.RegisterInstance(count);
return (CalculateFavoriteRank(childcontainer) + CalculateOutsiderRank(childcontainer))/2;
}
int CalculateFavoriteRank(IUnityContainer c)
{
c.RegisterInstance("velocity", GetFavoriteTeamVelocity());
var ranker = c.Resolve<IRanker>();
return ranker.Rank(GetFavoriteTeam());
}
int CalculateOutsiderRank(IUnityContainer c)
{
c.RegisterInstance("velocity", GetOutsiderTeamVelocity());
var ranker = c.Resolve<IRanker>();
return ranker.Rank(GetOutsiderTeam());
}
Здесь мы общие параметры (teams, count) кладём в контейнер сразу, а зависимые от контекста — ближе к вызову.CalculateOutsiderRank и CalculateFavoriteRank это вообще две разные реализации IRanker, а не методы ранжировщика. А velocity — вообще свойство команды.
Шаблон Data Dependency