Создание объектов классов ViewModels и Service

Я попытался понять создание классов ViewModels и Service и записал его для других. Пожалуйста, исправьте/добавьте, где необходимо.

Создание экземпляров ViewModels и сервисов выполняется не самым обычным способом. Это было сделано с использованием отражения.

В TipCalc у вас есть:

public class FirstViewModel : MvxViewModel
{
    private readonly ICalculationService _calculationService;

    public FirstViewModel(ICalculationService calculationService)
    {
        _calculationService = calculationService;
    }
...
}

и

public class App : Cirrious.MvvmCross.ViewModels.MvxApplication
{
    public override void Initialize()
    {
        CreatableTypes()
         .EndingWith("Service")
         .AsInterfaces()
         .RegisterAsLazySingleton();
    ...
    }
}

Во время инициализации() Интерфейсы и классы, созданные как Служба (имя заканчивается на Службе), спариваются с использованием отражений, имен интерфейса и имен классов (IPersonService и PersonService). Это позже используется для обратного просмотра экземпляра класса (таблица поиска содержит ленивые ссылки на экземпляры одноэлементных классов обслуживания. Службы создаются, когда null.

public FirstViewModel (ICalculationService calculateService) ссылается на экземпляр CalculationService. Это делается с использованием ранее созданной таблицы поиска.

Создание объектов ViewModels выполняется через структуру Mvx. Когда MvxFramework "спросит" о создании экземпляра ViewModel, он отобразит ViewModel и определит, какие конструкторы существуют в этом классе. Если есть конструктор без параметров, то это будет использоваться. Если есть конструктор с параметром, а параметр является интерфейсом класса сервиса, тогда в качестве параметра будет использоваться экземпляр (singleton) этой службы.

Службы создаются аналогичным образом; их конструкторы отражаются и создаются параметры (singleton). И так далее.

Ответ 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

Конструкция инжекции конструктора и 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 на всех платформах.
Использование плагина

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

Создание плагина

Написание плагинов легко сделать, но вначале может показаться немного сложным.

Ключевыми шагами являются:

  • Создайте основную сборку PCL для плагина - это должно включать:

    • интерфейсы, которые ваш плагин зарегистрирует
    • любой общий переносимый код (который может включать в себя реализации одного или нескольких интерфейсов)
    • специальный класс PluginLoader, который MvvmCross будет использовать для запуска плагина
  • При необходимости создайте узлы, специфичные для платформы, которые:

    • называются такими же, как и основная сборка, но с расширением платформы (.Droid,.WindowsPhone и т.д.).
    • содержит
      • любые реализации интерфейса платформы.
      • специальный класс Plugin, который MvvmCross будет использовать для запуска этого расширения для конкретной платформы
  • Дополнительно можно предоставить дополнительные документы, такие как документация и упаковка NuGet, которые облегчат повторное использование плагина.

Я больше не буду вдаваться в подробности написания плагинов.

Если вы хотите больше узнать о создании собственного плагина, выполните следующие действия:

Что делать, если...

Что делать, если... Я не хочу использовать 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