Я пытаюсь заставить Unity управлять созданием моих объектов, и я хочу иметь некоторые параметры инициализации, которые не известны до времени выполнения:
На данный момент единственный способ, которым я мог придумать, как это сделать - это использовать метод Init на интерфейсе.
interface IMyIntf {
void Initialize(string runTimeParam);
string RunTimeParam { get; }
}
Затем, чтобы использовать его (в Unity), я бы сделал это:
var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");
В этом сценарии runTimeParam
параметр определяется во время выполнения на основе пользовательского ввода. Тривиальный случай здесь просто возвращает значение, runTimeParam
но в действительности параметр будет выглядеть как имя файла, а метод initialize будет что-то делать с файлом.
Это создает ряд проблем, а именно то, что Initialize
метод доступен на интерфейсе и может вызываться несколько раз. Установка флага в реализации и создание исключения при повторном вызове Initialize
кажется неуклюжим.
В тот момент, когда я разрешаю свой интерфейс, я не хочу ничего знать о реализации IMyIntf
. Однако я хочу знать, что этот интерфейс требует определенных параметров инициализации. Есть ли способ как-то аннотировать (атрибуты?) Интерфейс этой информацией и передавать их в каркас при создании объекта?
Редактировать: описал интерфейс немного подробнее.
runTimeParam
это зависимость, которая определяется во время выполнения на основе пользовательского ввода. Должна ли альтернатива для этого делиться на два интерфейса - один для инициализации, а другой для хранения значений?Ответы:
В любом месте, где вам нужно значение времени выполнения для построения конкретной зависимости, абстрактная фабрика является решением.
Инициализация методов на интерфейсах пахнет утечкой абстракции .
В вашем случае я бы сказал, что вы должны смоделировать
IMyIntf
интерфейс на том, как вам нужно его использовать, а не на том, как вы намереваетесь создавать его реализации. Это деталь реализации.Таким образом, интерфейс должен быть просто:
Теперь определите абстрактную фабрику:
Теперь вы можете создать конкретную реализацию,
IMyIntfFactory
которая создает конкретные экземпляры,IMyIntf
подобные этой:Обратите внимание, как это позволяет нам защитить инварианты класса с помощью
readonly
ключевого слова. Никаких вонючих методов инициализации не требуется.IMyIntfFactory
Реализация может быть столь же простым , как это:Во всех ваших потребителях, где вам нужен
IMyIntf
экземпляр, вы просто берете зависимостьIMyIntfFactory
, запрашивая его через внедрение конструктора .Любой DI-контейнер, достойный своей соли, сможет автоматически подключить
IMyIntfFactory
экземпляр для вас, если вы зарегистрируете его правильно.источник
MyIntf
реализации фабрики требуется больше, чемrunTimeParam
(читай: другие службы, которые нужно разрешить с помощью IoC), то вы все равно сталкиваетесь с разрешением этих зависимостей на своей фабрике. Мне нравится ответ @PhilSandler о передаче этих зависимостей в конструктор фабрики, чтобы решить эту проблему - это ваше мнение ?Обычно, когда вы сталкиваетесь с такой ситуацией, вам нужно пересмотреть свой дизайн и определить, смешиваете ли вы объекты состояния / данных с вашими чистыми сервисами. В большинстве (не во всех) случаях вы хотите разделить эти два типа объектов.
Если вам нужен контекстно-зависимый параметр, передаваемый в конструкторе, один из вариантов - создать фабрику, которая разрешает ваши служебные зависимости через конструктор и принимает ваш параметр времени выполнения в качестве параметра метода Create () (или Generate ( ), Build () или как вы называете ваши фабричные методы).
Обычно сеттеры или метод Initialize () считаются плохим проектом, так как вам нужно «помнить», чтобы вызывать их, и убедиться, что они не слишком открывают состояние вашей реализации (т. Е. Что мешает кому-либо Вызов инициализации или сеттера?).
источник
Я также несколько раз сталкивался с этой ситуацией в средах, где я динамически создаю объекты ViewModel на основе объектов Model (очень хорошо обрисовано в этом другом посте Stackoverflow). ).
Мне понравилось, как расширение Ninject, которое позволяет динамически создавать фабрики на основе интерфейсов:
Bind<IMyFactory>().ToFactory();
Я не мог найти подобную функциональность непосредственно в Unity ; поэтому я написал свое собственное расширение для IUnityContainer, которое позволяет регистрировать фабрики, которые будут создавать новые объекты на основе данных из существующих объектов, по существу отображая из иерархии одного типа в иерархию другого типа: UnityMappingFactory @ GitHub
С целью простоты и удобочитаемости я получил расширение, которое позволяет вам напрямую указывать сопоставления, не объявляя отдельные фабричные классы или интерфейсы (экономия времени). Вы просто добавляете сопоставления там, где вы регистрируете классы во время обычного процесса начальной загрузки ...
Затем вы просто объявляете интерфейс фабрики отображения в конструкторе для CI и используете его метод Create () ...
В качестве дополнительного бонуса любые дополнительные зависимости в конструкторе отображаемых классов также будут разрешаться при создании объекта.
Очевидно, это не решит всех проблем, но до сих пор мне это очень хорошо помогло, поэтому я решил поделиться этим. На сайте проекта на GitHub больше документации.
источник
Я не могу ответить с определенной терминологией Unity, но, похоже, вы только что узнали о внедрении зависимости. Если это так, я призываю вас прочитать краткое, понятное и информативное руководство пользователя для Ninject. .
Это проведет вас через различные варианты, которые вы имеете при использовании DI, и как учесть конкретные проблемы, с которыми вы столкнетесь на этом пути. В вашем случае вы, скорее всего, захотите использовать DI-контейнер для создания экземпляров ваших объектов, и этот объект получит ссылку на каждую из его зависимостей через конструктор.
В пошаговом руководстве также подробно описано, как аннотировать методы, свойства и даже параметры, используя атрибуты, чтобы различать их во время выполнения.
Даже если вы не используете Ninject, пошаговое руководство предоставит вам концепции и терминологию функциональности, которая соответствует вашим целям, и вы сможете сопоставить эти знания с Unity или другими структурами DI (или убедить вас попробовать Ninject) ,
источник
Я думаю, что я решил это, и это кажется довольно полезным, так что это должно быть наполовину правильно :))
Я разделен
IMyIntf
на интерфейсы «getter» и «setter». Так:Тогда реализация:
IMyIntfSetter.Initialize()
все еще может вызываться несколько раз, но используя биты парадигмы Service Locator, мы можем довольно просто обернуть его так, чтоIMyIntfSetter
это почти внутренний интерфейс, отличный отIMyIntf
.источник