Unity 入門 - 延遲解析
本文大纲:
- 小引
- 共享的范例代码
- 使用 Lazy<T>
- 使用自动工厂
- 注入自定义工厂
小引
当我们说「解析某个型别/组件」时,意思通常是呼叫某类别的建构函式,以建立其实例(instance)。但有些场合,我们会希望解析时先不要生成对象,而是等到真正要呼叫对象的方法时才建立对象。这种延后建立对象的解析方式,叫做「延迟解析」(deferred resolution)。
延迟解析通常用在哪里呢?一个典型的场合是欲解析的对象的创建过程需要花较多时间(例如解析时可能因为建构函式需要注入其他对象,而产生多层巢状解析的情形),而我们希望能加快这个过程,以提升应用程序的响应速度。
本文介绍两种实现延迟解析的作法,一种是 Lazy<T>,另一种是「自动工厂」(automatic factories)。
共享的范例代码
为了避免往后重复太多相同的程序代码,这里先列出共享的接口与类别。
假设情境
假设应用程序需要提供讯息通知机制,而此机制需支持多种发送管道,例如:电子邮件、简讯服务(Short Message Service)、行动应用程序的讯息推送(push notification)等等。简单起见,这里仅实作其中两种服务,而且发送讯息的部分都使用简单的 Console.WriteLine() 来输出讯息,方便观察程序的执行结果。
设计
用一个 NotificationManager 类来作为整个讯息通知功能的管理员。各类讯息通知机制则由以下兩個类提供:
- EmailService:透过电子邮件发送讯息
- SmsService:透过简讯服务发送讯息
以上三个类均实作同一个接口: IMessageService, 而且 NotificationManager 只知道 IMessageService 接口,而不直接依赖具象类。下图描绘了它们的关系:
代码
讯息通知管理员的相关代码:
public interface INotificationManager
{
void Notify(string to, string msg);
} public class NotificationManager : INotificationManager
{
private readonly IMessageService _msgService = null; // 从建构函式注入讯息服务对象。
public NotificationManager(IMessageService svc)
{
_msgService = svc;
} // 利用讯息服务来发送讯息给指定对象。
public void Notify(string to, string msg)
{
_msgService.SendMessage(to, msg);
}
}
这里采用了 Constructor Injection 的注入方式,由建构函式传入讯息服务。其中的 Notify 方法则利用事先注入的讯息服务来发送讯息给指定的接收对象(自变量 "to")。在真实世界中,你可能会需要用额外的类别来代表讯息接收对象(例如设计一个 MessageRecipient 类 别来封装收件人的各项信息),这里为了示范而对这部分做了相当程度的简化。
底下是各类讯息服务的程序代码:
public interface IMessageService
{
void SendMessage(string to, string msg);
} public class EmailService : IMessageService
{
public void SendMessage(string to, string msg)
{
Console.WriteLine(" 透过 EmailService 发送邮件给 {0}。", to);
}
} public class SmsService : IMessageService
{
public void SendMessage(string to, string msg)
{
Console.WriteLine(" 透过 SmsService 发送简讯给 {0}。", to);
}
}
使用 Lazy<T>
Unity 可以让我们直接使用 .NET Framework 内建的 Lazy<T> 来实现延迟解析。试比较底下两个范例,首先是一般的写法:
// 一般写法
var container = new UnityContainer();
container.RegisterType<IMessageService, EmailService>(); // 注册 var svc = container.Resolve<IMessageService>(); // 解析组件(呼叫实作类别的建构函式)
svc.SendMessage("Michael", "Hello!"); // 使用组件
然后是 Lazy<T> 的延迟解析写法,由于注册型别的程序代码不变,故只列出解析的部分:
var lazyObj = container.Resolve<Lazy<IMessageService>>(); // 延迟解析
var svc = lazyObj.Value; // 此时才真正呼叫类别的建构函式
svc.SendMessage("Michael", "Hello!"); // 使用组件
注:有关 Lazy&T& 的用法,请参阅 MSDN 在线文件,或搜寻关键词「Lazy of T」。
使用自动工厂
「自动工厂」(automatic factories)指的是 DI 容器能够自动生成一个轻量级的工厂类别,这样我们就不用花工夫自己写了。那么,什么情况会用到自动工厂呢?通常是用来实现「延迟解析」,或者应付更复杂、更动态的晚期绑定(late binding)的需求。
仍旧以先前提过的 NotificationManager 和 IMessageService 为例。假设 EmailService 这个组件在建立实例时需要花费较长的时间(约五秒),参考以下程序片段:
public class EmailService : IMessageService
{
public EmailService()
{
// 以暂停线程的方式来仿真对象生成的过程需要花费较长时间。
System.Threading.Thread.Sleep();
} // 其余程序代码在这里并不重要,予以省略。
}
如果照一般的组件解析方式,会这么写:
// (1) 注册
var container = new UnityContainer();
container.RegisterType<IMessageService, EmailService>(); // (2) 解析
var notySvc = container.Resolve<NotificationManager>(); // (3) 呼叫
notySvc.Notify("Michael", "自动工厂范例");
这种写法,在其中的「(2) 解析」这个步骤会发生下列动作:
- DI 容器欲解析 NotificationManager,发现其建构函式需要传入 IMessageService 对象,于是先解析 IMessageService。
- 由于先前向容器注册组件时已经指定由 EmailService 来作为 IMessageService 的实作类别,故容器会先建立一个 EmailService 对象,然后将此对象传入 NotificationManager 的建构函式,以便建立一个 NotificationManager 对象。
也就是说,在解析 NotificationManager 时便一并建立了 EmailService 对象,故此步骤至少要花五秒的时间才能完成。然而,现在我们想要延后相依对象的创建时机,亦即等到真正呼叫组件的方法时,才真正建立其相依对象的实例。像这种场合,我们可以利用 Func<T> 与 Unity 的「自动工厂」来达到延迟解析相依对象的效果。作法很简单,只要修改 NotificationManager 类别就行了。如下所示:
class NotificationManager
{
private IMessageService _msgService;
private Func<IMessageService> _msgServiceFactory public NotificationManager(Func<IMessageService> svcFactory)
{
// 把工厂方法保存在委派对象里
_msgServiceFactory = svcFactory;
} public void Notify(string to, string msg)
{
// 由于每次呼叫 _msgServiceFactory() 时都会建立一个新的 IMessageService 对象,
// 这里用一个私有成员变量来保存先前建立的对象,以免不断建立新的实例。
// 当然这并非必要;有些场合,你可能会想要每次都建立新的相依对象。
if (_msgService == null)
{
_msgService = _msgServiceFactory();
} _msgService.SendMessage(to, msg);
}
}
另一方面,原先的「注册、解析、呼叫」三步骤的程序代码都不用任何改变。方便阅读起见,这里再将注册组件的程序代码贴上来:
// (1) 注册
var container = new UnityContainer();
container.RegisterType<IMessageService, EmailService>();
请注意,NotificationManager 的建构函式要求注入的明明是 Func<IMessageService>,可是向容器注册组件时,却依旧写 IMessageService,而不用改成 Func<IMessageService>(要五毛,给一块)。如此一来,当你想要为既有程序代码加入延迟解析(延迟建立相依对象)的能力时,就可以少改一些程序代码。这是 Unity 容器的「自动工厂」提供的好处。
进一步解释,当 Unity 容器欲解析 NotificationManager 时,发现其建构函式需要一个 Func<IMessageService> 委派(delegate),于是便自动帮你生成这个对象,并将它注入至 NotificationManager 类别的建构函式。由于注入的是委派对象(你可以把它当作是个工厂方法),故此时并没有真正建立 IMessageService 对象,而是等到上层模块呼叫此组件的 Notify 方法时,才透过呼叫委派方法来建立 IMessageService 对象 。
当然,Unity 容器的「自动工厂」可能无法满足某些需求。比如说,有些相依对象的创建逻辑比较复杂,需要你撰写自定义的对象工厂。这个时候,你可能会想要知道如何注入自定义工厂。
注入自定义工厂
当你想要让 Unity 容器在解析特定组件时使用你的自定义工厂来建立所需之相依对象,Unity 框架的 InjectionFactory 类别可以派上用场。
延续上一个小节的 NotificationManager 范例。现在假设你写了一个对象工厂来封装 IMessageService 的创建逻辑,像这样:
class MessageServiceFactory
{
public IMessageService GetService()
{
bool isEmail = CheckIfEmailIsUsed();
if (isEmail)
{
return new EmailService();
}
else
{
return new SmsService();
}
}
}
此对象工厂的 GetService 方法会根据执行时期的某些变量来决定要返回 EmailService 还是 SmsService 的实例。EmailService 与 SmsService 这两个类别都实作了 IMessageService 接口,它们的程序代码在这里并不重要,故未列出。如需查看这些类别的程序代码,可参阅稍早的〈共享的范例程序〉一节的内容。
NotificationManager 的建构函式与上一节的范例相同,仍旧是注入 Func<IMessageService>。如下所示:
class NotificationManager
{
private IMessageService _msgService;
private Func<IMessageService> _msgServiceFactory public NotificationManager(Func<IMessageService> svcFactory)
{
// 把工厂方法保存在委派对象里
_msgServiceFactory = svcFactory;
} // (已省略其他不重要的程序代码)
}
剩下的工作,就是告诉 Unity 容器:「在需要解析 IMessageService 的时候,请使用我的 MessageServiceFactory 来建立对象。」参考以下程序片段:
var container = new UnityContainer(); // 注册
Func<IMessageService> factoryMethod = new MessageServiceFactory().GetService;
container.RegisterType<IMessageService>(new InjectionFactory(c => factoryMethod())); // 解析
container.Resolve<NotificationManager>();
注册组件的部分需要加以说明,如下:
- 先建立一个 Func<IMessageService> 的委派对象,让它指向 MessageServiceFactory 对象的 GetService 方法。
- 接着呼叫 Unity 容器的 RegisterType 方法,告诉容器:解析 IMessageService 时,请用我提供的自定义工厂的 GetService 方法,而这个工厂方法已经包在刚才建立的委派对象(变量 factoryMethod),并透过 Unity 的 InjectionFactory 将此工厂方法再包一层,以便保存于 Unity 容器。
此范例所使用的 RegisterType 是个扩充方法,其原型宣告如下:
public static IUnityContainer RegisterType<T>(this IUnityContainer container,
params InjectionMember[] injectionMembers);
InjectionFactory 类别继承自 InjectionMember,而此范例所使用的建构函式之原型宣告为:
public InjectionFactory(Func<IUnityContainer, object> factoryFunc);
注:如需 InjectionFactory 类别的详细说明,可参考在线文件。
本文摘自:《 .NET 依赖注入 》第 7 章。
Unity 入門 - 延遲解析的更多相关文章
- 依賴注入入門——Unity(二)
參考博客文章http://www.cnblogs.com/kebixisimba/category/130432.html http://www.cnblogs.com/qqlin/tag/Unity ...
- Delphi APP 開發入門(七)通知與雲端推播
Delphi APP 開發入門(七)通知與雲端推播 分享: Share on facebookShare on twitterShare on google_plusone_share 閲讀次數: ...
- GOOGLE搜索從入門到精通V4.0
1,前言2,摘要3,如何使用本文4,Google簡介5,搜索入門6,初階搜索 6.1,搜索結果要求包含兩個及兩個以上關鍵字 6.2,搜索結果要求不包含某些特定資訊 6.3,搜索結果至少包含多個關鍵字中 ...
- Flask從入門到入土(三)——模板
模板是一個包含響應文本的文件,其中包含佔位變量表示的動態部分,其具體值只是請求上下文中才能知道.使用真實值替換變量,再返回最終得到的響應字符串,這一過程稱爲渲染.爲了渲染模板,Flask使用了一個名爲 ...
- Windows PowerShell 入門(7)-関数編2
この連載では.Microsoftが提供している新しいシェル.Windows Power Shellの使い方を解説します.前回に引き続きPowerShellにおける関数の取り扱いとして.変数と関数のスコ ...
- Windows PowerShell 入門(3)-スクリプト編
これまでの記事 Windows PowerShell 入門(1)-基本操作編 Windows PowerShell 入門(2)-基本操作編 2 対象読者 Windows PowerShellでコマンド ...
- Windows PowerShell 入門(2)-基本操作編 2
前回に引き続きMicrosoftが提供している新しいシェル.Windows Power Shellの基本操作方法を学びます.基本操作編第2弾の今回は.パイプの使用方法を中心としたコマンドレットの操作方 ...
- Delphi APP 開發入門(四)簡易手電筒
Delphi APP 開發入門(四)簡易手電筒 分享: Share on facebookShare on twitterShare on google_plusone_share 閲讀次數:32 ...
- Delphi APP 開發入門(六)Object Pascal 語法初探
Delphi APP 開發入門(六)Object Pascal 語法初探 分享: Share on facebookShare on twitterShare on google_plusone_sh ...
随机推荐
- 亲测有效,解决Can 't connect to local MySQL server through socket '/tmp/mysql.sock '(2) ";
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/hjf161105/article/details/78850658 最近租了一个阿里云云翼服务器,趁 ...
- 改变浏览器中默认的ctrl+s方法
在一般的情况下,我们在浏览网页的时候按下ctrl+s,浏览器会弹出一个保存网页的框. 但是在一些特定的网页中,我们希望ctrl+s不是弹出默认的保存窗口,而是进行一下别的操作. 比如在我们使用简书的时 ...
- eCognition学习记录
作者:朱金灿 来源:http://blog.csdn.net/clever101 昨天公司从外面请了人讲解eCognition的最新进展及项目二次开发应用情况.我做了大致下面记录: 1. eCogn ...
- E: Could not get lock /var/lib/dpkg/lock(无法获得锁)
出现这个问题可能是有另外一个程序正在运行,导致资源被锁不可用.而导致资源被锁的原因可能是上次运行安装或更新时没有正常完成,进而出现此状况,解决的办法其实很简单.有以下两种解决办法: 1. 强制解锁 执 ...
- .net命名空间和程序集详解
命名空间是一种用于将逻辑上相似的类按层次结构分组的机制.这种机制防止了命名冲突.在这种结构化采用被点号"."分隔的单词来实现.通常最顶层的命名空间是System,例如System; ...
- 西门子与三菱PLC报文比较
1.西门子和三菱的几个区别(上位只关心的通讯层面):1. 西门子PLC通讯端口固定102,但是可以连接多个PC端(客户端),三菱PLC通讯端口可以自定义,最多好像8个,但是每个端口只能连接一个客户端: ...
- Linux内核源代码情景分析-fork()
父进程fork子进程: child = fork() fork经过系统调用.来到了sys_fork.具体过程请參考Linux内核源码情景分析-系统调用. asmlinkage int sys_fork ...
- 张量(tensor)的理解
1. 从标量到矢量:携带更丰富的信息 矢,是箭的意思,突出的特点是其指向性. 袋子里有几个球? 3 个,magnitude(幅度,没有单位): 从这到你家多远?3 km(denominate),3 称 ...
- CMMI 能力成熟度模型集成
关于CMMI的过程域,请参考 CMMI能力成熟度模型集成的过程区域 1.CMMI/SPCA概述 CMM是“能力成熟度模型(Capability Maturity Model)”的英文简写,该模型由美国 ...
- what is the difference between definition and declaration in c
A declaration introduces an identifier and describes its type, be it a type, object, or function. A ...