ViewModels和Service类的实例化

Art*_*ter 10 xamarin.ios xamarin.android windows-phone mvvmcross windows-store-apps

我试图了解ViewModels和Service类的实例化,并将其写入其他人.请在需要的地方更正/添加.

ViewModels和Services的实例化不是以最常见的方式完成的.它是用反射完成的.

在TipCalc中你有:

public class FirstViewModel : MvxViewModel
{
    private readonly ICalculationService _calculationService;

    public FirstViewModel(ICalculationService calculationService)
    {
        _calculationService = calculationService;
    }
...
}
Run Code Online (Sandbox Code Playgroud)

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

在Initialize()期间,设计为Service的接口和类(名称以Service结尾)使用反射以及接口名称和类名称(IPersonService和PersonService)进行配对.这稍后用于反向查找类的实例(查找表包含对服务类的单例实例的延迟引用.服务在null时创建.

public FirstViewModel(ICalculationService calculationService)引用CalculationService的实例.这是通过使用先前创建的查找表来完成的.

ViewModels的实例化是通过Mvx框架完成的.当"询问"MvxFramework实例化的ViewModel时,它将反映ViewModel并确定该类上的构造函数.如果有一个无参数构造函数,那么将使用它.如果存在带参数的构造函数且参数是服务类的接口,则该服务的(单例)实例将用作参数.

服务以类似的方式实例化; 他们的构造函数反映和参数实例化(单例).等等.

Stu*_*art 62

这里使用的想法是:

  • 服务定位器模式
  • 控制反转

这里有很多文章和介绍 - 一些很好的起点是Martin Fowler的介绍Joel Abrahamsson的IoC介绍.我还制作了一些动画幻灯片作为一个简单的演示.


特别是在MvvmCross中,我们提供了一个静态类Mvx,它作为注册和解析接口及其实现的单一位置.

服务地点 - 注册和解决

MvvmCross Service Location的核心思想是你可以编写类和接口,如:

public interface IFoo
{
    string Request();
}

public class Foo : IFoo
{
    public string Request()
    {
        return "Hello World";
    }
}
Run Code Online (Sandbox Code Playgroud)

单身人士登记

编写这一对后,您可以将Foo实例注册为单例,实现IFoo使用:

// every time someone needs an IFoo they will get the same one
Mvx.RegisterSingleton<IFoo>(new Foo());
Run Code Online (Sandbox Code Playgroud)

如果你这样做,那么任何代码都可以调用:

    var foo = Mvx.Resolve<IFoo>();
Run Code Online (Sandbox Code Playgroud)

并且每次调用都会返回相同的Foo 实例.

懒惰的单身注册

作为对此的变体,您可以注册一个懒惰的单身人士.这是写的

// 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());
Run Code Online (Sandbox Code Playgroud)

在这种情况下:

  • 没有Foo在最初创建
  • 任何代码第一次调用时,都会创建并返回Mvx.Resolve<IFoo>()一个新代码Foo
  • 所有后续调用都将获得第一次创建的同一实例

'动态'注册

最后一个选择是,你可以注册IFooFoo配对:

// every time someone needs an IFoo they will get a new one
Mvx.RegisterType<IFoo, Foo>();
Run Code Online (Sandbox Code Playgroud)

在这种情况下,每次调用Mvx.Resolve<IFoo>()都会创建一个新的Foo- 每个调用都会返回一个不同的调用Foo.

最后注册的胜利

如果您创建了几个接口实现并将它们全部注册:

Mvx.RegisterType<IFoo, Foo1>();
Mvx.RegisterSingleton<IFoo>(new Foo2());
Mvx.RegisterType<IFoo, Foo3>();
Run Code Online (Sandbox Code Playgroud)

然后每次调用都会替换以前的注册 - 因此当客户端调用时,Mvx.Resolve<IFoo>()将返回最近的注册.

这可用于:

  • 覆盖默认实现
  • 根据应用程序状态替换实现 - 例如,在用户经过身份验证之后,您可以IUserInfo使用真实实现替换空实现.

按公约批量注册

MvvmCross的默认NuGet模板在核心中包含一段代码,App.cs如:

CreatableTypes()
    .EndingWith("Service")
    .AsInterfaces()
    .RegisterAsLazySingleton();
Run Code Online (Sandbox Code Playgroud)

此代码使用Reflection来:

  • 找到Core程序集中的所有类
    • creatable- 即:
      • 有一个公共构造函数
      • 不是 abstract
    • 名称以Service结尾
  • 找到他们的接口
  • 根据它们支持的接口将它们注册为懒惰单例

技术说明:懒惰的单身执行这里是相当的技术-它可以确保如果一个类实现IOneITwo再同一个实例解析时都返回IOneITwo.

在这里结束的名称选择Service- 以及使用懒惰单身人士的选择只是个人惯例.如果您希望为对象使用其他名称或其他生命周期,则可以使用其他调用或多次调用替换此代码,例如:

CreatableTypes()
    .EndingWith("SingleFeed")
    .AsInterfaces()
    .RegisterAsLazySingleton();
CreatableTypes()
    .EndingWith("Generator")
    .AsInterfaces()
    .RegisterAsDynamic();
CreatableTypes()
    .EndingWith("QuickSand")
    .AsInterfaces()
    .RegisterAsSingleton();
Run Code Online (Sandbox Code Playgroud)

Linq如果您愿意,您还可以使用其他帮助方法来帮助进一步定义您的注册 - 例如Inherits,Except.WithAttribute,Containing,InNamespace...如

        CreatableTypes()
            .StartingWith("JDI")
            .InNamespace("MyApp.Core.HyperSpace")
            .WithAttribute(typeof(MySpecialAttribute))
            .AsInterfaces()
            .RegisterAsSingleton();
Run Code Online (Sandbox Code Playgroud)

当然,您也可以在核心以外的程序集上使用相同类型的注册逻辑 - 例如:

typeof(Reusable.Helpers.MyHelper).Assembly.CreatableTypes()
    .EndingWith("Helper")
    .AsInterfaces()
    .RegisterAsDynamic();
Run Code Online (Sandbox Code Playgroud)

或者,如果您不想使用此基于反射的注册,那么您只需手动注册您的实现:

Mvx.RegisterSingleton<IMixer>(new MyMixer());
Mvx.RegisterSingleton<ICheese>(new MyCheese());
Mvx.RegisterType<IBeer, Beer>();
Mvx.RegisterType<IWine, Wine>();
Run Code Online (Sandbox Code Playgroud)

选择权归你的.

构造函数注入

同样Mvx.Resolve<T>,Mvx静态类提供了一种基于反射的机制,可在对象构造期间自动解析参数.

例如,如果我们添加一个类,如:

public class Bar
{
    public Bar(IFoo foo)
    {
        // do stuff
    }
}
Run Code Online (Sandbox Code Playgroud)

然后您可以使用以下命令创建此对象

    Mvx.IocConstruct<Bar>();
Run Code Online (Sandbox Code Playgroud)

这次通话期间会发生什么:

  • MvvmCross:
    • 使用Reflection来查找构造函数 Bar
    • 查看该构造函数的参数并看到它需要一个 IFoo
    • 用于Mvx.Resolve<IFoo>()获取已注册的实现IFoo
    • 使用Reflection用IFoo参数调用构造函数

构造函数注入和ViewModel

在创建ViewModel时,此"构造函数注入"机制在MvvmCross内部使用.

如果您声明一个ViewModel:

 public class MyViewModel : MvxViewModel
 {
     public MyViewModel(IMvxJsonConverter jsonConverter, IMvxGeoLocationWatcher locationWatcher)
     {
        // ....
     }
 }
Run Code Online (Sandbox Code Playgroud)

然后MvvmCross将使用Mvx静态类来解决的对象为jsonConverterlocationWatcher一个时MyViewModel被创建.

这很重要,因为:

  1. 它允许您locationWatcher在不同平台上轻松提供不同的课程(在iPhone上您可以使用与之交谈的观察者CoreLocation,在Windows Phone上您可以使用与之交谈的观察者System.Device.Location
  2. 它允许您在单元测试中轻松提供模拟实现
  3. 它允许您覆盖默认实现 - 如果您不喜欢Json.NetJson 的实现,则可以使用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...
}
Run Code Online (Sandbox Code Playgroud)

如果您将此计算器注册为:

Mvx.RegisterType<ITaxCalculator, TaxCalculator>();
Run Code Online (Sandbox Code Playgroud)

然后,当客户端调用Mvx.Resolve<ITaxCalculator>()然后MvvmCross将创建一个新TaxCalculator的实例,解决所有的ICustomerRepository,IForeignExchangeITaxRuleList在操作过程中.

此外,此过程是递归的 - 因此,如果这些返回的对象中的任何一个需要另一个对象 - 例如,如果您的IForeignExchange实现需要一个IChargeCommission对象 - 那么MvvmCross也将为您提供Resolve.

当我需要在不同平台上实现不同的实现时,如何使用IoC?

有时您需要在ViewModel中使用某些特定于平台的功能.例如,您可能希望在ViewModel中获取当前屏幕尺寸 - 但是没有现成的便携式.Net调用来执行此操作.

当您想要包含这样的功能时,有两个主要选择:

  1. 在核心库中声明一个接口,然后在每个UI项目中提供并注册一个实现.
  2. 使用或创建插件

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); }
    }
}
Run Code Online (Sandbox Code Playgroud)

在每个UI项目中,您可以为其声明特定于平台的实现IScreenSize.一个简单的例子是:

public class WindowsPhoneScreenSize : IScreenSize
{
    public double Height { get { return 800.0; } }
    public double Width { get { return 480.0; } }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以在每个特定平台的安装文件的注册这些实现-例如,你可以覆盖MvxSetup.InitializeFirstChance

protected override void InitializeFirstChance()
{
    Mvx.RegisterSingleton<IScreenSize>(new WindowsPhoneScreenSize());
    base.InitializeFirstChance();
}
Run Code Online (Sandbox Code Playgroud)

完成此操作后,MyViewModelIScreenSize在每个平台上提供正确的特定于平台的实现.

2.使用或创建插件

插件是用于组合PCL组件的MvvmCross图案,加上任选以打包某些功能的一些特定于平台的组件.

这个插件层只是一个模式 - 一些简单的约定 - 用于命名相关的程序集,包括小型PluginLoaderPlugin辅助类,以及使用IoC.通过这种模式,它可以轻松地跨平台和跨应用程序包含,重用和测试功能.

例如,现有插件包括:

  • 一个File插件,提供System.IO对操作文件的类型方法的访问
  • 一个Location插件,提供对GeoLocation信息的访问
  • 一个Messenger插件,提供对Messenger/Event Aggregator的访问
  • 一个PictureChooser插件,提供对相机和媒体库的访问
  • 一个ResourceLoader插件,它提供了一种访问应用程序的.apk,.app或.ipa中打包的资源文件的方法
  • 一个SQLite插件,提供SQLite-net对所有平台的访问.
插件使用

如果您想了解如何在您的应用程序中使用这些插件,那么:

插件创作

编写插件很容易,但起初可能会感到有些畏惧.

关键步骤是:

  1. 为插件创建主PCL程序集 - 这应该包括:

    • 您的插件将注册的接口
    • 任何共享的可移植代码(可能包括一个或多个接口的实现)
    • PluginLoaderMvvmCross将用于启动插件的特殊类
  2. (可选)创建特定于平台的程序集:

    • 命名与主程序集相同,但具有特定于平台的扩展名(.Droid,.WindowsPhone等)
    • 包含
      • 任何特定于平台的接口实现
      • PluginMvvmCross将用于启动此特定于平台的扩展的特殊类
  3. 可选择提供文档和NuGet打包等附加功能,使插件更易于重用.

我不打算在这里详细介绍插件.

如果您想了解更多关于编写自己的插件的信息,那么:

如果...

如果......我不想使用服务位置或IoC

如果您不想在代码中使用它,那么请不要.

只需CreatableTypes()...从App.cs中删除代码,然后在ViewModels中使用"普通代码" - 例如:

public class MyViewModel : MvxViewModel
{
    private readonly ITaxService _taxService;

    public MyViewModel()
    {
        _taxService = new TaxService();
    }
}
Run Code Online (Sandbox Code Playgroud)

如果......我想使用不同的服务位置或IoC机制

那里有很多优秀的库,包括AutoFac,Funq,MEF,OpenNetCF,TinyIoC以及许多其他库!

如果要替换MvvmCross实现,则需要:

  • 写一些Adapter层来提供他们的服务位置代码IMvxIoCProvider
  • CreateIocProvider在您的Setup类中覆盖以提供此替代IMvxIoCProvider实现.

或者,您可以组织混合情况 - 两个IoC/ServiceLocation系统并排存在.

如果......我想使用Property Injection作为IoC机制

提供了IoC的Property Injection实现示例.

这可以使用Setup覆盖来初始化:

protected override IMvxIoCProvider CreateIocProvider()
{
    return MvxPropertyInjectingIoCContainer.Initialise();
}
Run Code Online (Sandbox Code Playgroud)

如果......我想要像子容器这样的高级IoC功能

MvvmCross中的IoC容器设计得非常轻巧,并且针对我构建的移动应用程序所需的功能级别.

如果您需要更高级/复杂的功能,那么您可能需要使用不同的提供程序或不同的方法 - 对此的一些建议将在以下内容中讨论:MvvmCross IoC中的子容器

  • 希望我能给这个答案多一个upvote! (3认同)
  • 哇.非常好的答案! (2认同)