ViewModels和Service类的实例化

时间:2013-06-06 16:33:32

标签: xamarin.ios xamarin.android windows-store-apps windows-phone mvvmcross

我试图了解ViewModels和Service类的实例化,并将其写下来供其他人使用。请在需要的地方更正/加载。

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

在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();
    ...
    }
}

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

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

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

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

1 个答案:

答案 0 :(得分:62)

这里使用的想法是:

  • 服务定位器模式
  • 控制倒置

有很多关于此的文章和介绍 - 一些好的起点是Martin Fowler's introductionJoel Abrahamsson's IoC introduction。我还做了一些animated slides作为一个简单的演示。


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

服务地点 - 注册和解决方案

MvvmCross服务位置的核心思想是您可以编写类和接口,如:

public interface IFoo
{
    string Request();
}

public class Foo : IFoo
{
    public string Request()
    {
        return "Hello World";
    }
}

单身人士登记

编写完这一对后,您可以将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的相同实例

懒惰单身注册

作为对此的修改,您可以注册一个懒惰的单身人士。这是写的

// 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());

在这种情况下:

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

&#39;动态&#39;注册

最后一个选项是,您可以将IFooFoo对注册为:

// 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实现替换为真实实现。

按大会批量注册

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

CreatableTypes()
    .EndingWith("Service")
    .AsInterfaces()
    .RegisterAsLazySingleton();

此代码使用Reflection来:

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

技术说明:这里的懒惰单例实现非常技术性 - 它确保如果一个类实现IOneITwo,那么在解析两者时将返回相同的实例IOneITwo

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

CreatableTypes()
    .EndingWith("SingleFeed")
    .AsInterfaces()
    .RegisterAsLazySingleton();
CreatableTypes()
    .EndingWith("Generator")
    .AsInterfaces()
    .RegisterAsDynamic();
CreatableTypes()
    .EndingWith("QuickSand")
    .AsInterfaces()
    .RegisterAsSingleton();

如果您愿意,还可以使用其他Linq辅助方法来帮助您进一步定义注册 - 例如InheritsExceptWithAttributeContainingInNamespace ......例如

        CreatableTypes()
            .StartingWith("JDI")
            .InNamespace("MyApp.Core.HyperSpace")
            .WithAttribute(typeof(MySpecialAttribute))
            .AsInterfaces()
            .RegisterAsSingleton();

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

typeof(Reusable.Helpers.MyHelper).Assembly.CreatableTypes()
    .EndingWith("Helper")
    .AsInterfaces()
    .RegisterAsDynamic();

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

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

这&#34;构造函数注入&#34;在创建ViewModel时,在MvvmCross内部使用机制。

如果您声明ViewModel,请执行以下操作:

 public class MyViewModel : MvxViewModel
 {
     public MyViewModel(IMvxJsonConverter jsonConverter, IMvxGeoLocationWatcher locationWatcher)
     {
        // ....
     }
 }

然后,当创建Mvx时,MvvmCross将使用jsonConverter静态类来解析locationWatcherMyViewModel的对象。

这很重要因为:

  1. 它允许您在不同平台上轻松提供不同的locationWatcher课程(在iPhone上您可以使用与CoreLocation对话的观察者,在Windows Phone上,您可以使用与{{1}对话的观察者}}
  2. 它允许您在单元测试中轻松提供模拟实现
  3. 它允许您覆盖默认实现 - 如果您不喜欢Json的System.Device.Location实现,则可以使用Json.Net实现。
  4. 构造函数注入和链接

    在内部,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>(); 时,MvvmCross将创建一个新的Mvx.Resolve<ITaxCalculator>()实例,在操作期间解析所有TaxCalculatorICustomerRepositoryIForeignExchange

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

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

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

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

    1. 在核心库中声明一个接口,然后在每个UI项目中提供并注册一个实现。
    2. 使用或创建插件
    3. 1。具有平台特定实现的PCL接口

      在您的核心项目中,您可以声明一个接口,并且可以在您的类中使用该接口 - 例如:

      IChargeCommission

      在每个UI项目中,您可以为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; } } }
      MvxSetup.InitializeFirstChance

      完成此操作后,protected override void InitializeFirstChance() { Mvx.RegisterSingleton<IScreenSize>(new WindowsPhoneScreenSize()); base.InitializeFirstChance(); } 将在每个平台上为MyViewModel提供正确的特定于平台的实现。

      2。使用或创建插件

      插件是一个MvvmCross模式,用于组合PCL程序集,以及可选的某些特定于平台的程序集,以便打包一些功能。

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

      例如,现有插件包括:

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

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

      插件创作

      编写插件很容易,但最初会感到有点令人生畏。

      关键步骤是:

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

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

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

      4. 我不打算在此处详细介绍插件。

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

        如果......

        如果......我不想使用服务地点或IoC

        如果您不想在代码中使用此功能,请不要这样做。

        只需从App.cs中删除Vibrate代码,然后使用&#39;普通代码&#39;在你的ViewModels中 - 例如:

        CreatableTypes()...

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

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

        如果您想要替换MvvmCross实现,那么您需要:

        • 编写某种public class MyViewModel : MvxViewModel { private readonly ITaxService _taxService; public MyViewModel() { _taxService = new TaxService(); } } 图层,以便将其服务位置代码作为Adapter
        • 提供
        • 覆盖IMvxIoCProvider课程中的CreateIocProvider,以提供此替代Setup实施。

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

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

        IoC提供了一个示例Property Injection实现。

        可以使用以下设置覆盖来初始化:

        IMvxIoCProvider

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

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

        如果您需要更高级/复杂的功能,那么您可能需要使用不同的提供商或不同的方法 - 有关此问题的一些建议将在以下讨论:Child containers in MvvmCross IoC