11

我试图理解 ViewModels 和 Service 类的实例化并为其他人写下来。请在需要的地方更正/添加。

ViewModel 和服务的实例化不是以最常见的方式完成的。它是使用反射完成的。

在 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 calculateService) 引用 CalculationService 的一个实例。这是通过使用之前创建的查找表来完成的。

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

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

4

1 回答 1

63

这里使用的想法是:

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

有很多关于这方面的文章和介绍——一些很好的起点是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";
    }
}

单例注册

编写完这对代码后,您可以将一个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());

在这种情况下:

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

“动态”注册

最后一种选择是,您可以将IFooandFoo对注册为:

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

此代码使用反射来:

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

技术说明:这里的惰性单例实现是相当技术性的——它确保如果一个类实现了IOne,那么在解析和ITwo时将返回相同的实例。IOneITwo

选择以此处结尾的名称 - Service- 以及选择使用 Lazy 单例只是个人约定。如果您喜欢为您的对象使用其他名称或其他生命周期,您可以将此代码替换为不同的调用或多个调用,例如:

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

Linq如果您愿意,您还可以使用其他辅助方法来帮助进一步定义您的注册 - 例如InheritsExcept. 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();

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

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:
    • 使用反射找到构造函数Bar
    • 查看该构造函数的参数,发现它需要一个IFoo
    • 用于Mvx.Resolve<IFoo>()获取已注册的实现IFoo
    • 使用反射调用带IFoo参数的构造函数

构造函数注入和视图模型

这种“构造函数注入”机制在创建 ViewModel 时在 MvvmCross 内部使用。

如果您声明一个 ViewModel,例如:

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

然后 MvvmCross 将使用Mvx静态类来解析对象jsonConverter以及locationWatcher何时MyViewModel创建 a。

这很重要,因为:

  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...
}

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

Mvx.RegisterType<ITaxCalculator, TaxCalculator>();

然后,当客户端调用时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); }
    }
}

在每个 UI 项目中,您可以为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将为每个平台提供正确的特定IScreenSize于平台的实现。

2.使用或创建插件

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

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

例如,现有插件包括:

  • 一个文件插件,它提供System.IO对操作文件的类型方法的访问
  • 提供对 GeoLocation 信息的访问的 Location 插件
  • 一个 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 中删除代码,然后在您的 ViewModel 中使用“普通代码” - 例如:

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 系统并排存在。

如果... 我想使用属性注入作为 IoC 机制

提供了一个用于 IoC 的示例属性注入实现。

这可以使用以下设置覆盖进行初始化:

protected override IMvxIoCProvider CreateIocProvider()
{
    return MvxPropertyInjectingIoCContainer.Initialise();
}

如果... 我想要高级 IoC 功能,例如子容器

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

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

于 2013-06-07T09:03:12.067 回答