这里使用的想法是:
有很多关于这方面的文章和介绍 - 一些很好的起点是马丁·福勒简介 http://www.martinfowler.com/articles/injection.html and Joel Abrahamsson 的 IoC 介绍 http://joelabrahamsson.com/inversion-of-control-an-introduction-with-examples-in-net/。我也做了一些动画幻灯片 https://github.com/slodge/MvvmCross-Presentations/blob/master/MvxDay/InterfaceDrivenDevelopment.pptx作为一个简单的演示。
特别是在 MvvmCross 中,我们提供了一个静态类Mvx
它充当注册和解析接口及其实现的单一位置。
服务地点-登记与解决
MvvmCross Service Location 的核心思想是您可以编写如下类和接口:
public interface IFoo
{
string Request();
}
public class Foo : IFoo
{
public string Request()
{
return "Hello World";
}
}
单例注册
写完这对后,您就可以注册一个Foo
实例作为单例实现IFoo
using:
// every time someone needs an IFoo they will get the same one
Mvx.RegisterSingleton<IFoo>(new Foo());
如果你这样做了,那么任何代码都可以调用:
var foo = Mvx.Resolve<IFoo>();
每个调用都会返回同一个实例 of 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());
在这种情况下:
- no
Foo
最初被创建
- 第一次调用任何代码
Mvx.Resolve<IFoo>()
然后是一个新的Foo
将被创建并返回
- 所有后续调用都将获得第一次创建的相同实例
“动态”注册
最后一个选择是您可以注册IFoo
and 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>();
然后每次调用replaces之前的注册 - 所以当客户打电话时Mvx.Resolve<IFoo>()
然后将返回最近的注册信息。
这对于以下方面很有用:
- 覆盖默认实现
- 根据应用程序状态替换实现 - 例如用户通过身份验证后,您可以替换空的
IUserInfo
与真实的实施。
按惯例批量注册
MvvmCross 的默认 NuGet 模板在核心中包含一个代码块App.cs
like:
CreatableTypes()
.EndingWith("Service")
.AsInterfaces()
.RegisterAsLazySingleton();
此代码使用反射来:
- find all classes in the Core assembly
- which are
creatable
- i.e.:
- 有一个公共构造函数
- are not
abstract
- 名称以 Service 结尾
- 找到他们的接口
- 根据它们支持的接口将它们注册为惰性单例
技术说明:这里的惰性单例实现非常技术性 - 它确保如果一个类实现IOne
and ITwo
那么在解析两者时将返回相同的实例IOne
and 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();
或者,如果您不想使用这种基于反射的注册,那么您可以手动注册您的实现:
Mvx.RegisterSingleton<IMixer>(new MyMixer());
Mvx.RegisterSingleton<ICheese>(new MyCheese());
Mvx.RegisterType<IBeer, Beer>();
Mvx.RegisterType<IWine, Wine>();
选择是yours.
构造函数注入
也Mvx.Resolve<T>
, the Mvx
静态类提供了基于反射的机制来在对象构造期间自动解析参数。
例如,如果我们添加一个类:
public class Bar
{
public Bar(IFoo foo)
{
// do stuff
}
}
然后你可以使用以下方法创建这个对象:
Mvx.IocConstruct<Bar>();
这次通话期间发生的事情是:
- MvvmCross:
- 使用Reflection来查找构造函数
Bar
- 查看该构造函数的参数,发现它需要一个
IFoo
- uses
Mvx.Resolve<IFoo>()
获取已注册的实现IFoo
- 使用反射来调用构造函数
IFoo
范围
构造函数注入和 ViewModel
创建 ViewModel 时,这种“构造函数注入”机制在 MvvmCross 内部使用。
如果您声明一个 ViewModel,如下所示:
public class MyViewModel : MvxViewModel
{
public MyViewModel(IMvxJsonConverter jsonConverter, IMvxGeoLocationWatcher locationWatcher)
{
// ....
}
}
那么 MvvmCross 将使用Mvx
用于解析对象的静态类jsonConverter
and locationWatcher
when a 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
and ITaxRuleList
在操作过程中。
进一步地,这个过程是递归的- 所以如果这些返回的对象中的任何一个需要另一个对象 - 例如如果你的IForeignExchange
实施需要一个IChargeCommission
对象 - 那么 MvvmCross 也会为您提供 Resolve。
当我需要在不同平台上实现不同的实现时,如何使用 IoC?
有时您需要在 ViewModel 中使用某些特定于平台的功能。例如,您可能想要获取 ViewModel 中的当前屏幕尺寸 - 但没有现有的可移植 .Net 调用来执行此操作。
当您想要包含这样的功能时,有两个主要选择:
- 在核心库中声明一个接口,然后在每个 UI 项目中提供并注册一个实现。
- 使用或创建一个plugin
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
with
protected override void InitializeFirstChance()
{
Mvx.RegisterSingleton<IScreenSize>(new WindowsPhoneScreenSize());
base.InitializeFirstChance();
}
完成此操作后,然后MyViewModel
将获得正确的特定于平台的实现IScreenSize
在每个平台上。
2. 使用或创建plugin
A Plugin是一种 MvvmCross 模式,用于组合 PCL 程序集以及可选的一些特定于平台的程序集以打包某些功能。
这个插件层只是一个模式 - 一些简单的约定 - 用于命名相关的程序集,包括小的PluginLoader
and Plugin
辅助类,以及使用 IoC。通过这种模式,它允许跨平台和跨应用程序轻松地包含、重用和测试功能。
例如,现有的插件包括:
- 一个文件插件,提供访问
System.IO
操作文件的类型方法
- 一个位置插件,提供对地理位置信息的访问
- 一个 Messenger 插件,提供对 Messenger/事件聚合器的访问
- PictureChooser 插件,提供对相机和媒体库的访问
- ResourceLoader 插件提供了一种访问应用程序打包在 .apk、.app 或 .ipa 中的资源文件的方法
- 一个 SQLite 插件,提供访问
SQLite-net
在所有平台上。
Plugin Use
如果您想了解如何在您的应用程序中使用这些插件,那么:
- the N+1 videos provide a good starting point - see http://mvvmcross.wordpress.com/ http://mvvmcross.wordpress.com/ - especially :
- N=8 - 位置http://slodge.blogspot.co.uk/2013/05/n8-location-location-location-n1-days.html http://slodge.blogspot.co.uk/2013/05/n8-location-location-location-n1-days.html
- N=9 - 信使http://slodge.blogspot.co.uk/2013/05/n9-getting-message-n1-days-of-mvvmcross.html http://slodge.blogspot.co.uk/2013/05/n9-getting-message-n1-days-of-mvvmcross.html
- N=10 - SQLitehttp://slodge.blogspot.co.uk/2013/05/n10-sqlite-persistent-data-storage-n1.html http://slodge.blogspot.co.uk/2013/05/n10-sqlite-persistent-data-storage-n1.html
- N=12 -> N=17 - Collect-A-Bull 应用程序http://slodge.blogspot.co.uk/2013/05/n12-collect-bull-full-app-part-1-n1.html http://slodge.blogspot.co.uk/2013/05/n12-collect-bull-full-app-part-1-n1.html
Plugin Authoring
编写插件很容易,但一开始可能会感觉有点令人畏惧。
关键步骤是:
-
为插件创建主 PCL 程序集 - 这应包括:
- 您的插件将注册的接口
- 任何共享的可移植代码(可能包括一个或多个接口的实现)
- 一个特别的
PluginLoader
MvvmCross 将使用该类来启动插件
-
可以选择创建特定于平台的程序集,其中:
- 与主程序集的名称相同,但具有特定于平台的扩展名(.Droid、.WindowsPhone 等)
- contains
- 任何特定于平台的接口实现
- 一个特别的
Plugin
MvvmCross 将使用该类来启动此特定于平台的扩展
-
可以选择提供文档和 NuGet 打包等额外内容,这将使插件更易于重用。
我不打算在这里详细介绍如何编写插件。
如果您想了解有关编写自己的插件的更多信息,那么:
- 有一个关于这个的演示https://speakerdeck.com/cirrious/plugins-in-mvvmcross https://speakerdeck.com/cirrious/plugins-in-mvvmcross
- 有一个示例创建了一个
Vibrate
插件位于https://github.com/slodge/MvvmCross-Tutorials/tree/master/GoodVibrations https://github.com/slodge/MvvmCross-Tutorials/tree/master/GoodVibrations
如果什么...
如果...我不想使用服务位置或 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 中的子容器 https://stackoverflow.com/questions/16514691/child-containers-in-mvvmcross-ioc