Ответ 1
Идеи, которые используются здесь:
- шаблон локатора службы
- Инверсия управления
На этом есть много статей и интродукций - некоторые хорошие стартовые места - введение Мартина Фаулера и Введение Joel Abrahamsson IoC. Я также сделал несколько анимированных слайдов в качестве простой демонстрации.
В частности, в MvvmCross мы предоставляем один статический класс Mvx
, который действует как одно место для регистрации и разрешения интерфейсов и их реализации.
Местоположение службы - Регистрация и разрешение
Основная идея MvvmCross Service Location заключается в том, что вы можете писать классы и интерфейсы, например:
public interface IFoo
{
string Request();
}
public class Foo : IFoo
{
public string Request()
{
return "Hello World";
}
}
Регистрация Singleton
С помощью этой пары вы можете зарегистрировать экземпляр Foo
в качестве сингла, который реализует IFoo
, используя:
// every time someone needs an IFoo they will get the same one
Mvx.RegisterSingleton<IFoo>(new Foo());
Если вы это сделали, то любой код может вызвать:
var foo = Mvx.Resolve<IFoo>();
и каждый отдельный вызов вернет тот же самый экземпляр для Foo.
Регистрация Lazy Singleton
Как вариант, вы можете зарегистрировать ленивый синглтон. Это написано
// every time someone needs an IFoo they will get the same one
// but we don't create it until someone asks for it
Mvx.RegisterSingleton<IFoo>(() => new Foo());
В этом случае:
- no
Foo
создается изначально - при первом вызове кода
Mvx.Resolve<IFoo>()
будет создан и возвращен новыйFoo
- все последующие вызовы получат тот же самый экземпляр, который был создан в первый раз
"Динамическая регистрация"
Один из последних вариантов состоит в том, что вы можете зарегистрировать пару IFoo
и Foo
как:
// every time someone needs an IFoo they will get a new one
Mvx.RegisterType<IFoo, Foo>();
В этом случае каждый вызов Mvx.Resolve<IFoo>()
создаст новый Foo
- каждый вызов вернет другой Foo
.
Последние зарегистрированные победы
Если вы создаете несколько реализаций интерфейса и регистрируете их все:
Mvx.RegisterType<IFoo, Foo1>();
Mvx.RegisterSingleton<IFoo>(new Foo2());
Mvx.RegisterType<IFoo, Foo3>();
Затем каждый вызов заменяет предыдущую регистрацию - поэтому, когда клиент вызывает Mvx.Resolve<IFoo>()
, будет возвращена самая последняя регистрация.
Это может быть полезно для:
- перезапись реализаций по умолчанию
- замена реализаций в зависимости от состояния приложения - например. после того, как пользователь был аутентифицирован, вы можете заменить пустую реализацию
IUserInfo
на реальную.
Массовая регистрация по Конвенции
Шаблоны NuGet по умолчанию для MvvmCross содержат блок кода в ядре App.cs
, например:
CreatableTypes()
.EndingWith("Service")
.AsInterfaces()
.RegisterAsLazySingleton();
Этот код использует Reflection to:
- найти все классы в сборке Core
- которые
creatable
- то есть:- имеет открытый конструктор
- не
abstract
- с именами, заканчивающимися в Service
- которые
- найти свои интерфейсы
- регистрировать их как ленивые синглтоны в соответствии с интерфейсами, которые они поддерживают
Техническая нота: ленивая реализация синглтона здесь довольно техническая - она гарантирует, что если класс реализует IOne
и ITwo
, то тот же экземпляр будет возвращен при разрешении как IOne
, так и ITwo
.
Выбор названия, заканчивающийся здесь - Service
- и выбор использования ленивых синглетов - это только личные соглашения. Если вы предпочитаете использовать другие имена или другие сроки жизни для своих объектов, вы можете заменить этот код другим вызовом или несколькими вызовами, например:
CreatableTypes()
.EndingWith("SingleFeed")
.AsInterfaces()
.RegisterAsLazySingleton();
CreatableTypes()
.EndingWith("Generator")
.AsInterfaces()
.RegisterAsDynamic();
CreatableTypes()
.EndingWith("QuickSand")
.AsInterfaces()
.RegisterAsSingleton();
Там вы также можете использовать дополнительные вспомогательные методы Linq
, чтобы помочь вам определить ваши регистрации, если вы хотите - например. Inherits
, Except
. WithAttribute
, Containing
, InNamespace
... например
CreatableTypes()
.StartingWith("JDI")
.InNamespace("MyApp.Core.HyperSpace")
.WithAttribute(typeof(MySpecialAttribute))
.AsInterfaces()
.RegisterAsSingleton();
И вы также можете использовать логику логики того же типа для сборок, кроме Core - например:
typeof(Reusable.Helpers.MyHelper).Assembly.CreatableTypes()
.EndingWith("Helper")
.AsInterfaces()
.RegisterAsDynamic();
В качестве альтернативы, если вы предпочитаете не использовать эту регистрацию на основе Reflection, тогда вы можете просто вручную зарегистрировать свои реализации:
Mvx.RegisterSingleton<IMixer>(new MyMixer());
Mvx.RegisterSingleton<ICheese>(new MyCheese());
Mvx.RegisterType<IBeer, Beer>();
Mvx.RegisterType<IWine, Wine>();
Выбор ваш.
Инъекция конструктора
Как и Mvx.Resolve<T>
, статический класс Mvx
предоставляет механизм, основанный на отражении, для автоматического разрешения параметров во время построения объекта.
Например, если добавить класс, например:
public class Bar
{
public Bar(IFoo foo)
{
// do stuff
}
}
Затем вы можете создать этот объект, используя:
Mvx.IocConstruct<Bar>();
Что происходит во время этого вызова:
- MvvmCross:
- использует Reflection для поиска конструктора
Bar
- рассматривает параметры этого конструктора и видит, что ему нужен
IFoo
- использует
Mvx.Resolve<IFoo>()
, чтобы получить зарегистрированную реализацию дляIFoo
- использует Reflection для вызова конструктора с параметром
IFoo
- использует Reflection для поиска конструктора
Конструкция инжекции конструктора и ViewModels
Этот механизм "Конструктор Инъекции" используется внутри MvvmCross при создании ViewModels.
Если вы объявляете ViewModel следующим:
public class MyViewModel : MvxViewModel
{
public MyViewModel(IMvxJsonConverter jsonConverter, IMvxGeoLocationWatcher locationWatcher)
{
// ....
}
}
то MvvmCross будет использовать статический класс Mvx
для разрешения объектов для jsonConverter
и locationWatcher
при создании MyViewModel
.
Это важно, потому что:
- Это позволяет вам легко предоставлять разные классы
locationWatcher
на разных платформах (на iPhone вы можете использовать наблюдателя, который разговаривает сCoreLocation
, на Windows Phone вы можете использовать наблюдателя, который говорит сSystem.Device.Location
- Это позволяет вам легко выполнять макетные реализации в модульных тестах.
- Это позволяет переопределять реализации по умолчанию - если вам не нравится реализация
Json.Net
для Json, вы можете вместо этого использовать реализациюServiceStack.Text
.
Инъекция и цепочка конструктора
Внутри механизм Mvx.Resolve<T>
использует инъекцию конструктора, когда нужны новые объекты.
Это позволяет вам регистрировать реализации, которые зависят от других интерфейсов, таких как:
public interface ITaxCalculator
{
double TaxDueFor(int customerId)
}
public class TaxCalculator
{
public TaxCalculator(ICustomerRepository customerRepository, IForeignExchange foreignExchange, ITaxRuleList taxRuleList)
{
// code...
}
// code...
}
Если вы затем зарегистрируете этот калькулятор как:
Mvx.RegisterType<ITaxCalculator, TaxCalculator>();
Затем, когда клиент вызывает Mvx.Resolve<ITaxCalculator>()
, тогда MvvmCross создаст новый экземпляр TaxCalculator
, разрешив все ICustomerRepository
, IForeignExchange
и ITaxRuleList
во время операции.
Кроме того, этот процесс рекурсивный - поэтому, если для любого из этих возвращенных объектов требуется другой объект - например, если для реализации IForeignExchange
требуется объект IChargeCommission
- тогда MvvmCross предоставит вам Resolve.
Как использовать IoC, когда мне нужны разные реализации на разных платформах?
Иногда вам нужно использовать определенные функции платформы в своих моделях ViewModels. Например, вы можете захотеть получить текущие размеры экрана в ViewModel, но нет существующего портативного вызова .Net, чтобы сделать это.
Если вы хотите включить такие функции, то есть два основных варианта:
- Объявить интерфейс в вашей основной библиотеке, но затем предоставить и зарегистрировать реализацию в каждом из ваших проектов пользовательского интерфейса.
- Использовать или создавать плагин
1. PCL-интерфейс с реализацией платформы
В своем основном проекте вы можете объявить интерфейс, и вы можете использовать этот интерфейс в своих классах - например:
public interface IScreenSize
{
double Height { get; }
double Width { get; }
}
public class MyViewModel : MvxViewModel
{
private readonly IScreenSize _screenSize;
public MyViewModel(IScreenSize screenSize)
{
_screenSize = screenSize;
}
public double Ratio
{
get { return (_screenSize.Width / _screenSize.Height); }
}
}
В каждом проекте пользовательского интерфейса вы можете объявить реализацию конкретной платформы для IScreenSize
. Тривиальный пример:
public class WindowsPhoneScreenSize : IScreenSize
{
public double Height { get { return 800.0; } }
public double Width { get { return 480.0; } }
}
Затем вы можете зарегистрировать эти реализации в каждом из файлов Setup для конкретной платформы - например, вы можете переопределить MvxSetup.InitializeFirstChance
с помощью
protected override void InitializeFirstChance()
{
Mvx.RegisterSingleton<IScreenSize>(new WindowsPhoneScreenSize());
base.InitializeFirstChance();
}
С этим сделано, MyViewModel
получит соответствующую платформенную реализацию IScreenSize
на каждой платформе.
2. Используйте или создайте плагин
Плагин - это шаблон MvvmCross для объединения сборки PCL, а также, возможно, некоторые узлы, специфичные для платформы, для упаковки некоторых функций.
Этот слой плагина представляет собой просто шаблон - некоторые простые соглашения - для присвоения имен Assemblies, включая небольшие вспомогательные классы PluginLoader
и Plugin
, а также для использования IoC. Благодаря этому шаблону он позволяет легко включать, повторно использовать и тестировать функциональные возможности на разных платформах и в разных приложениях.
Например, существующие плагины включают в себя:
- Плагин файлов, который предоставляет доступ к
System.IO
методам типа для управления файлами - Плагин местоположения, который обеспечивает доступ к информации GeoLocation
- плагин Messenger, который обеспечивает доступ к агрегатору Messenger/Event
- плагин PictureChooser, который обеспечивает доступ к камере и медиа-библиотеке
- плагин ResourceLoader, который обеспечивает способ доступа к файлам ресурсов, упакованным в .apk,.app или .ipa для приложения
- плагин SQLite, который обеспечивает доступ к
SQLite-net
на всех платформах.
Если вы хотите посмотреть, как эти плагины могут использоваться в ваших приложениях, выполните следующие действия:
- видео N + 1 обеспечивают хорошую отправную точку - см. http://mvvmcross.wordpress.com/ - особенно:
- N = 8 - Местоположение http://slodge.blogspot.co.uk/2013/05/n8-location-location-location-n1-days.html
- N = 9 - Messenger http://slodge.blogspot.co.uk/2013/05/n9-getting-message-n1-days-of-mvvmcross.html
- N = 10 - SQLite http://slodge.blogspot.co.uk/2013/05/n10-sqlite-persistent-data-storage-n1.html
- N = 12 → N = 17 - приложение Collect-A-Bull http://slodge.blogspot.co.uk/2013/05/n12-collect-bull-full-app-part-1-n1.html
Написание плагинов легко сделать, но вначале может показаться немного сложным.
Ключевыми шагами являются:
-
Создайте основную сборку PCL для плагина - это должно включать:
- интерфейсы, которые ваш плагин зарегистрирует
- любой общий переносимый код (который может включать в себя реализации одного или нескольких интерфейсов)
- специальный класс
PluginLoader
, который MvvmCross будет использовать для запуска плагина
-
При необходимости создайте узлы, специфичные для платформы, которые:
- называются такими же, как и основная сборка, но с расширением платформы (.Droid,.WindowsPhone и т.д.).
- содержит
- любые реализации интерфейса платформы.
- специальный класс
Plugin
, который MvvmCross будет использовать для запуска этого расширения для конкретной платформы
-
Дополнительно можно предоставить дополнительные документы, такие как документация и упаковка NuGet, которые облегчат повторное использование плагина.
Я больше не буду вдаваться в подробности написания плагинов.
Если вы хотите больше узнать о создании собственного плагина, выполните следующие действия:
- там есть презентация на https://speakerdeck.com/cirrious/plugins-in-mvvmcross
- есть образец, который создает плагин
Vibrate
на https://github.com/slodge/MvvmCross-Tutorials/tree/master/GoodVibrations
Что делать, если...
Что делать, если... Я не хочу использовать Service Location или IoC
Если вы не хотите использовать это в своем коде, не делайте этого.
Просто удалите код CreatableTypes()...
из App.cs, а затем используйте "обычный код" в ваших моделях ViewModels - например:
public class MyViewModel : MvxViewModel
{
private readonly ITaxService _taxService;
public MyViewModel()
{
_taxService = new TaxService();
}
}
Что делать, если... Я хочу использовать другое расположение службы или механизм IoC
Существует множество библиотек отлично, включая AutoFac, Funq, MEF, OpenNetCF, TinyIoC и многие другие!
Если вы хотите заменить реализацию MvvmCross, вам необходимо:
- напишите какой-то слой
Adapter
, чтобы предоставить код своей службы в видеIMvxIoCProvider
- переопределить
CreateIocProvider
в классеSetup
, чтобы обеспечить эту альтернативную реализациюIMvxIoCProvider
.
В качестве альтернативы вы можете организовать гибридную ситуацию - где две системы IoC/ServiceLocation существуют бок о бок.
Что делать, если... Я хочу использовать Property Injection как механизм IoC
Для реализации IoC существует пример реализации инжекции объектов.
Это можно инициализировать с помощью переопределения установки:
protected override IMvxIoCProvider CreateIocProvider()
{
return MvxPropertyInjectingIoCContainer.Initialise();
}
Что делать, если... Мне нужны расширенные функции IoC, такие как дочерние контейнеры
Контейнер IoC в MvvmCross разработан достаточно лёгким и ориентирован на уровень функциональности, необходимый для мобильных приложений, которые я создал.
Если вам нужны более сложные/сложные функции, вам может потребоваться использовать другой провайдер или другой подход. Некоторые предложения для этого обсуждаются в: Контейнеры для детей в MvvmCross IoC