Более интеллектуальный контейнер внедрения зависимостей для Xamarin.Forms — Marcus Technical Services

Это просто внедрение зависимостей — вот и все, ребята.

Современным программистам требуются некоторые средства для создания новых классов из других классов, которые передаются в качестве параметров, и делать это элегантно и плавно. Эти новые экземпляры часто требуют кэширование, особенно если это сервисы или модели представлений. Наилучшим решением этой проблемы было неправильно названное и совершенно неправильно описанное «Контейнер МОК». В другая статья, я объяснил, почему это плохая идея. Если мы не можем даже назвать что-то точно, пора полностью это пересмотреть. Так что я сделал это здесь с помощью инструмента, который действительно нужен всем нам: DI («Внедрение зависимости») Контейнер.

Что в этом такого умного?

1. Он обеспечивает истинное _ управление жизненным циклом _

Контейнеры IOC всегда сохраняют экземпляр при его создании. Это очень _ расточительный _. Это также _ небезопасный _. Умный DI-контейнер предоставляет только три вида регистрации:

  • Любой уровень доступа / Не хранить: Используйте их для создания экземпляров в любое время и без кэширования. Локальная переменная, которую вы извлекаете в Resolve(), является единственной сохраненной ссылкой.

  • Совместно между экземплярами: Когда вы используете Resolve(), используя этот уровень доступа, вы должны передать «родительский» объект, который индексируется для этого нового экземпляра класса. Вы также можете связать один и тот же экземпляр с любым количеством других потребителей/родителей, снова вызвав Resolve(). Как только все родители умирают, кэшированный экземпляр также удаляется. Примечание. Для этого требуется вызвать событие Xamarin.Forms, чтобы уведомить контейнер о смерти родителя общего экземпляра. Мы делаем это для вас, если вы также используете наш Библиотека с учетом жизненного цикла.

  • Глобальный синглтон: Контейнер создает и кэширует постоянный экземпляр любого типа, зарегистрированный с этим уровнем доступа. Кэшированная ссылка умирает, когда сам контейнер выходит из области видимости.

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

2. Это не глобально и не статично

Вы можете объявить экземпляр Умный DI-контейнер где угодно. Это поддерживает «вложенные» сценарии, в которых контейнеры живут внутри узко определенных деревьев наследования классов. Запомнить: все «глобальные» переменные, сохраненные/кэшированные, будут жить только до тех пор, пока существует контейнер.

3. Хорошо себя ведет

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

_container = new SmartDIContainer(); _container.RegisterTypeAsInterface<FirstSimpleClass>(typeof(IAmSimple)); _container.RegisterTypeAsInterface<SecondSimpleClass>(typeof(IAmSimple));

… а затем решить Я простая вы создали _ конфликт _. Контейнер не может знать, какой из них вернуть. Вы можете установить логическое свойство для выдачи ошибки в этом случае. Или вы можете предоставить средство разрешения конфликтов:

var simple = _container.Resolve<IAmSimple>(StorageRules.AnyAccessLevel, null, ForbidSpecificClass<FirstSimpleClass>); private static IConflictResolution ForbidSpecificClass<T>(IDictionary<Type, ITimeStampedCreatorAndStorageRules> registrations) { // Find any registration where the key (the main class that was registered and that is being constructed) is *not* the forbidden one var legalValues = registrations.Where(r => r.Key != typeof(T)).ToArray(); if (legalValues.IsEmpty()) { return null; } return new ConflictResolution { MasterType = legalValues.First().Key, TypeToCastWithStorageRule = legalValues.First().Value.CreatorsAndStorageRules.First() }; }

4. Это крошечный

Умный DI-контейнер почти не занимает места и редко трогает память, так как не хранит ничего лишнего.

5. Это простой, честный C# с открытым исходным кодом, который легко читать.

Мы действительно добавили комментарии! (И нас не поразила молния)

6. Это проверено и доказано

См. модульные тесты.

Быстрый старт

Создавайте DI-контейнеры везде, где они вам нужны; Не беспокойтесь о централизации

Контейнеры DI регистрируют и предоставляют доступ к переменным. Чтобы оставаться в пределах С# ТВЕРДЫЙ Руководство, ваше приложение должно быть как _ конфиденциальный, насколько это возможно _. Так что последнее, что вам нужно, это глобальные контейнеры. Услуги являются заметным исключением. Они должны быть доступны во всем приложении. Так app.xaml.cs может выглядеть так:

public partial class App : Application, IManagePageChanges, IReportAppLifecycle { public static readonly ISmartDIContainer GlobalServiceContainer = new SmartDIContainer(); public App() { InitializeComponent(); // Required by IOS MainPage = new ContentPage(); GlobalServiceContainer.RegisterTypeAsInterface<GlobalServiceOne>(typeof(IGlobalServiceOne), StorageRules.GlobalSingleton); GlobalServiceContainer.RegisterTypeAsInterface<GlobalServiceTwo>(typeof(IGlobalServiceTwo), StorageRules.GlobalSingleton); GlobalServiceContainer.RegisterTypeAsInterface<GlobalServiceThree>(typeof(IGlobalServiceThree), StorageRules.GlobalSingleton); GlobalServiceContainer.RegisterTypeAsInterface<ViewModelFactory>(typeof(IViewModelFactory)); // Start up the navigation system StateMachine.ResetCurrentPageMode(); } }

Создание фабрики моделей представлений

public class ViewModelFactory : IViewModelFactory { #region Private Fields private readonly SmartDIContainerWithLifecycle _viewModelContainer = new SmartDIContainerWithLifecycle(); #endregion Private Fields #region Public Constructors public ViewModelFactory(IGlobalServiceOne service1, IGlobalServiceTwo service2, IGlobalServiceThree service3) { // Register the services as singletons _viewModelContainer.RegisterTypeAsInterface<GlobalServiceOne>(typeof(IGlobalServiceOne), StorageRules.GlobalSingleton); _viewModelContainer.RegisterTypeAsInterface<GlobalServiceTwo>(typeof(IGlobalServiceTwo), StorageRules.GlobalSingleton); _viewModelContainer.RegisterTypeAsInterface<GlobalServiceThree>(typeof(IGlobalServiceThree), StorageRules.GlobalSingleton); // Register other known types using various access levels _viewModelContainer.RegisterTypeAsInterface<ViewModel_Private>(typeof(IViewModel_Private), StorageRules.DoNotStore); _viewModelContainer.RegisterTypeAsInterface<ViewModel_ToBeShared>(typeof(IViewModel_ToBeShared), StorageRules.SharedDependencyBetweenInstances); _viewModelContainer.RegisterTypeAsInterface<ViewModel_Global>(typeof(IViewModel_Global), StorageRules.GlobalSingleton); } #endregion Public Constructors #region Public Methods /// <summary> /// This case requires an object "parent". /// </summary> public ICustomViewModelBase CreateSharedViewModel<T>(object obj) where T : class, ICustomViewModelBase { return _viewModelContainer.Resolve<T>(boundInstance: obj); } /// <summary> /// This method will return the view model based on its registration rules. /// It is safer than registering as "All Access" and then resolving using more narrow guidance. /// It also encapsulates the private (static) view model container, insulating it from the rest of the app. /// </summary> public ICustomViewModelBase CreateViewModel<T>() where T : class, ICustomViewModelBase { return _viewModelContainer.Resolve<T>(); }

Мы добавляем ViewModel.Utils чтобы дать нам доступ к фабрике моделей представления:

public static class ViewModelUtils { /// <summary> /// Get the view model factory out of the main container; the services are provided at the same time. /// </summary> public static readonly IViewModelFactory ViewModelBuilder = App.GlobalServiceContainer.Resolve<IViewModelFactory>(); }

Посмотреть фабрику моделей требует трех служб в своем конструкторе. Путем разрешения фабрики из глобального контейнера (окончание в app.xaml.cs )эти сервисы внедряются автоматически, поэтому теперь они доступны для использования здесь.

Создайте базу модели просмотра, чтобы выполнить тяжелую работу для моделей просмотра.

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

[AddINotifyPropertyChangedInterface] public class CustomViewModelBase : ViewModelWithLifecycle, ICustomViewModelBase { public ICommand ButtonCommand => new Command(StateMachine.GoToNextMode); public string Content { get; set; } public string Description { get; set; } public string Title { get; set; } }

Добавьте несколько моделей просмотра

Они гиперпросты; базовый класс делает все за них. Обратите внимание, что конструкторы запрашивают услуги. Это свидетельствует о том, что эстафетная палочка передается от app.xaml.cs Контейнер DI до нашего собственного контейнера DI был успешным.

public class ViewModel_Global : CustomViewModelBase, IViewModel_Global { public ViewModel_Global(IGlobalServiceTwo service2, IGlobalServiceThree service3) {} } public class ViewModel_Private : CustomViewModelBase, IViewModel_Private { public ViewModel_Private(IGlobalServiceOne service1, IGlobalServiceThree service3) {} } public class ViewModel_ToBeShared : CustomViewModelBase, IViewModel_ToBeShared { public ViewModel_ToBeShared(IGlobalServiceTwo service2) {} }

Создайте страницу для отображения данных модели просмотра

Ваш пользовательский интерфейс будет намного сложнее. В этом примере показано, как использовать одну страницу для отображения различных моделей представления. Помните: в реальном программировании на C# нет принудительное выравнивание между страницей/представлением и его моделью представления. Это установлено динамично в _ время выполнения _ на основе живые условия и бизнес-логика.

На этой странице используется информация о жизненном цикле ContentPageWithLifecycleчто настоятельно рекомендуется.

<?xml version="1.0" encoding="utf-8" ?> <pages:ContentPageWithLifecycle xmlns=" xmlns:x=" xmlns:pages="clr-namespace:Com.MarcusTS.LifecycleAware.Views.Pages;assembly=Com.MarcusTS.LifecycleAware" x:Class="Com.MarcusTS.SmartDI.LifecycleAware.SampleApp.Views.GeneralPage"> <pages:ContentPageWithLifecycle.Content> <StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand" Margin="50" Spacing="25"> <Label Text="{Binding Title}" FontSize="32" FontAttributes="Bold" VerticalOptions="Center" HorizontalOptions="Center" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" /> <Label Text="{Binding Description}" FontSize="18" FontAttributes="Italic" VerticalOptions="Center" HorizontalOptions="Center" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" /> <Label Text="{Binding Content}" FontSize="32" TextColor="DarkGreen" FontAttributes="Bold" VerticalOptions="Center" HorizontalOptions="Center" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" /> <Button Command="{Binding ButtonCommand}" BackgroundColor="DarkBlue" WidthRequest="100" HeightRequest="50" Text="NEXT" FontSize="18" TextColor="White" FontAttributes="Bold" VerticalOptions="Center" HorizontalOptions="Center" /> </StackLayout> </pages:ContentPageWithLifecycle.Content> </pages:ContentPageWithLifecycle>

Создайте конечный автомат для навигации, а также для определения того, какая страница идет с какой моделью представления в этот момент.

public static class StateMachine { public enum PageModes { Private_1, Private_2, Shared_1, Shared_2, Global_1, Global_2, END } static StateMachine() { _privateViewModel1.Title = "Private View Model #1"; _privateViewModel1.Description = "This view model's content is separate from any other view model. It is never stored globally."; _privateViewModel1.Content = "C A T"; _privateViewModel2.Title = "Private View Model #2"; _privateViewModel2.Description = "This view model's content is also private, but the content has changed. It is completely different from the previous view model."; _privateViewModel2.Content = "C H A I R"; _sharedViewModel1.Description = "This view model is shared, so no matter how many copies we Resolve they are always the same. They are stored globally until their parent pages fall out of scope."; _sharedViewModel1.Content = "H O U S E"; // No need to set _sharedViewModel2; it is the same memory reference as _sharedViewModel1 _globalViewModel1.Description = "This view model is global, so, like shared, no matter how many copies we Resolve they are always the same. They are stored globally for the life of the container, which, in this case, is the life olf the app."; _globalViewModel1.Content = "W A T E R"; // No need to set _globalViewModel2; it is the same memory reference as _globalViewModel1 _endViewModel.Title = "END"; _endViewModel.Description = "To restart, click 'NEXT'"; } private static PageModes CurrentPageMode { get => _currentPageMode; set { _currentPageMode = value; switch (_currentPageMode) { case PageModes.Private_1: SetMainPage(_generalPage1, _privateViewModel1); break; case PageModes.Private_2: SetMainPage(_generalPage2, _privateViewModel2); break; case PageModes.Shared_1: // We over-write the shared title for the sake of clarity _sharedViewModel1.Title = "Shared View Model #1"; SetMainPage(_generalPage1, _sharedViewModel1); break; case PageModes.Shared_2: // We over-write the shared title for the sake of clarity _sharedViewModel2.Title = "Shared View Model #2"; SetMainPage(_generalPage2, _sharedViewModel2); break; case PageModes.Global_1: // We over-write the global title for the sake of clarity _globalViewModel1.Title = "Global View Model #1"; SetMainPage(_generalPage1, _globalViewModel1); break; case PageModes.Global_2: // We over-write the global title for the sake of clarity _globalViewModel1.Title = "Global View Model #2"; SetMainPage(_generalPage2, _globalViewModel2); break; default: SetMainPage(_generalPage1, _endViewModel); break; } } } private static void SetMainPage(ContentPage page, IViewModelWithLifecycle viewModel) { page.BindingContext = viewModel; Application.Current.MainPage = page; } public static void GoToNextMode() { if ((int) CurrentPageMode < Enum.GetValues(typeof(PageModes)).Length - 1) { CurrentPageMode = (PageModes) ((int) CurrentPageMode + 1); } else { ResetCurrentPageMode(); } } public static void ResetCurrentPageMode() { CurrentPageMode = 0; } private static PageModes _currentPageMode; private static readonly ICustomViewModelBase _endViewModel = ViewModelUtils.ViewModelBuilder.CreateViewModel<IViewModel_Private>(); private static readonly ContentPage _generalPage1 = new GeneralPage(); private static readonly ContentPage _generalPage2 = new GeneralPage(); private static readonly ICustomViewModelBase _globalViewModel1 = ViewModelUtils.ViewModelBuilder.CreateViewModel<IViewModel_Global>(); private static readonly ICustomViewModelBase _globalViewModel2 = ViewModelUtils.ViewModelBuilder.CreateViewModel<IViewModel_Global>(); private static readonly ICustomViewModelBase _privateViewModel1 = ViewModelUtils.ViewModelBuilder.CreateViewModel<IViewModel_Private>(); private static readonly ICustomViewModelBase _privateViewModel2 = ViewModelUtils.ViewModelBuilder.CreateViewModel<IViewModel_Private>(); private static readonly ICustomViewModelBase _sharedViewModel1 = ViewModelUtils.ViewModelBuilder.CreateSharedViewModel<IViewModel_ToBeShared>(_generalPage1); private static readonly ICustomViewModelBase _sharedViewModel2 = ViewModelUtils.ViewModelBuilder.CreateSharedViewModel<IViewModel_ToBeShared>(_generalPage2); }

Начни это

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

.

public App() { // code omitted ... StateMachine.ResetCurrentPageMode(); }

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

См. источник

Все это доступно бесплатно на Гитхаб.

Чтобы добавить Smart DI Container в собственное приложение, включите следующие пакеты NuGet:

Com.MarcusTS.SmartDI
Com.MarcusTS.SmartDI.Lifecycle
Com.MarcusTS.LifecycleAware

Приложение: Жизнь без руководства с учетом жизненного цикла

Если вы просто не можете использовать Руководство по жизненному циклу по какой-либо причине, Умный DI-контейнер по-прежнему лучший DI-контейнер, который вы можете использовать, по всем причинам, изложенным здесь. Вот как вы можете действовать без полного руководства:

  1. Включите базовый Nuget Smart Di Container: Com.MarcusTS.SmartDI

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

  • Вы регистрируете объект (например, модель представления) которыми вы хотите поделиться, указав Правило хранения в качестве SharedDependencyBetweenInstances
  • Вы разрешаете экземпляр из контейнера, отправляя родителя, которого хотите связать с экземпляром. Для модели представления вы, вероятно, захотите связать страницу, которая действует как ее родитель.
  • Когда родитель умирает, модель представления должна быть удалена из контейнера. Но без Руководство по жизненному циклу вы должны сделать это самостоятельно.
  1. Всякий раз, когда связанный родитель умирает, и вы хотите, чтобы его «дочерняя» модель представления была удалена из контейнера, вызовите этот SmartDIContainer метод: ContainerObjectIsDisappearing (объект containerObj). Передайте родителя, который теперь вне области видимости. Делать _ нет _ передать сохраняемую модель представления.
  2. Умный DI-контейнер достаточно «умен», чтобы понять, что модель представления осиротела, поэтому автоматически удалит ее.

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *