【问题标题】:Instantiation of ViewModels and Service classesViewModel 和服务类的实例化
【发布时间】:2013-06-06 16:33:32
【问题描述】:

我试图理解 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 并确定该类上有哪些构造函数。如果有一个无参数的构造函数,那么它将被使用。如果有带参数的构造函数,并且参数是服务类的接口,那么该服务的(单例)实例将被用作参数。

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

【问题讨论】:

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


    【解决方案1】:

    这里使用的想法是:

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

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


    特别是在 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());
    

    在这种情况下:

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

    “动态”注册

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

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

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

    最后登记的胜利

    如果您创建一个接口的多个实现并将它们全部注册:

    Mvx.RegisterType<IFoo, Foo1>();
    Mvx.RegisterSingleton<IFoo>(new Foo2());
    Mvx.RegisterType<IFoo, Foo3>();
    

    然后每次调用替换之前的注册 - 所以当客户调用 Mvx.Resolve&lt;IFoo&gt;() 时,将返回最近的注册。

    这可以用于:

    • 覆盖默认实现
    • 根据应用程序状态替换实现 - 例如用户通过身份验证后,您可以将空的 IUserInfo 实现替换为真实的实现。

    按惯例批量注册

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

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

    这段代码使用反射来:

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

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

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

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

    如果您愿意,您还可以使用其他 Linq 辅助方法来帮助进一步定义您的注册 - 例如InheritsExceptWithAttribute, 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&lt;T&gt; 一样,Mvx 静态类提供了一种基于反射的机制,可在对象构造期间自动解析参数。

    例如,如果我们添加如下类:

    public class Bar
    {
        public Bar(IFoo foo)
        {
            // do stuff
        }
    }
    

    然后您可以使用以下方法创建此对象:

        Mvx.IocConstruct<Bar>();
    

    在此通话期间发生的情况是:

    • MvvmCross:
      • 使用反射找到Bar的构造函数
      • 查看该构造函数的参数,发现它需要一个IFoo
      • 使用Mvx.Resolve&lt;IFoo&gt;() 获取IFoo 的注册实现
      • 使用反射调用带有IFoo参数的构造函数

    构造函数注入和视图模型

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

    如果你声明一个 ViewModel 像:

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

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

    这很重要,因为:

    1. 它允许您在不同平台上轻松提供不同的locationWatcher 类(在iPhone 上您可以使用与CoreLocation 对话的观察者,在Windows Phone 上您可以使用与System.Device.Location 对话的观察者
    2. 它允许您在单元测试中轻松提供模拟实现
    3. 它允许您覆盖默认实现 - 如果您不喜欢 Json 的 Json.Net 实现,您可以改用 ServiceStack.Text 实现。

    构造函数注入和链接

    在内部,Mvx.Resolve&lt;T&gt; 机制在需要新对象时使用构造函数注入。

    这使您能够注册依赖于其他接口的实现,例如:

    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&lt;ITaxCalculator&gt;()时,MvvmCross会创建一个新的TaxCalculator实例,在操作过程中解析所有ICustomerRepositoryIForeignExchangeITaxRuleList

    此外,这个过程是递归的 - 所以如果这些返回的对象中的任何一个需要另一个对象 - 例如如果您的 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 类型文件操作方法的访问
    • 一个位置插件,提供对地理位置信息的访问
    • 一个 Messenger 插件,提供对 Messenger/Event Aggregator 的访问权限
    • 一个 PictureChooser 插件,提供对相机和媒体库的访问权限
    • 一个 ResourceLoader 插件,它提供了一种方法来访问打包在应用程序的 .apk、.app 或 .ipa 中的资源文件
    • 一个 SQLite 插件,可在所有平台上提供对 SQLite-net 的访问。
    插件使用

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

    插件创作

    编写插件很容易,但一开始可能会感到有些畏惧。

    关键步骤是:

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

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

      • 与主程序集命名相同,但具有特定于平台的扩展名(.Droid、.WindowsPhone 等)
      • 包含
        • 任何特定于平台的接口实现
        • 一个特殊的Plugin 类,MvvmCross 将使用它来启动这个特定于平台的扩展
    3. 可选地提供额外的文档和 NuGet 打包,这将使插件更易于重用。

    我不会在这里详细介绍如何编写插件。

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

    如果...

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

    如果您不想在代码中使用它,那就不要。

    只需从 App.cs 中删除 CreatableTypes()... 代码,然后在您的 ViewModel 中使用“普通代码” - 例如:

    public class MyViewModel : MvxViewModel
    {
        private readonly ITaxService _taxService;
    
        public MyViewModel()
        {
            _taxService = new TaxService();
        }
    }
    

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

    有很多优秀的库,包括 AutoFac、Funq、MEF、OpenNetCF、TinyIoC 等等!

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

    • 编写某种Adapter 层以将其服务位置代码提供为IMvxIoCProvider
    • 在您的 Setup 类中覆盖 CreateIocProvider 以提供此替代 IMvxIoCProvider 实现。

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

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

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

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

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

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

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

    如果您需要更高级/更复杂的功能,那么您可能需要使用不同的提供者或不同的方法 - 对此的一些建议在以下位置进行了讨论:Child containers in MvvmCross IoC

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-01
      • 1970-01-01
      • 2020-04-10
      • 1970-01-01
      相关资源
      最近更新 更多