1.1 控制反轉
在面向對象設計的軟件系統中,它的底層都是由N個對象構成的,各個對象之間通過相互合作,最終實現系統的業務邏輯。同時,對象之間的耦合關系是無法避免的,也是必要的,這是協同工作的基礎。但是,伴隨着工業級應用的規模越來越龐大,對象之間的依賴關系也越來越復雜,經常會出現對象之間的多重依賴性關系,因此,架構師和設計師對於系統的分析和設計,將面臨更大的挑戰。對象之間耦合度過高的系統,必然會出現牽一發而動全身的情形。
耦合關系不僅會出現在對象與對象之間,也會出現在軟件系統的各模塊之間,以及軟件系統和硬件系統之間。如何降低系統之間、模塊之間和對象之間的耦合度,是軟件工程永遠追求的目標之一。為了解決對象之間的耦合度過高的問題,軟件專家Michael Mattson 1996年提出了IOC理論,用來實現對象之間的“解耦”.
我們來看下面的示例。
示例1
| public class EmailService { public void SendMessage() { Console.WriteLine("通過電子郵件發送消息"); } } public class MessageManager { private EmailService msgService; public MessageManager() { msgService=new EmailService(); } public void SendMsg() { msgService.SendMessage(); } } |
在示例1中,MessageManager 類依賴於EmailService類,兩者之間存在耦合。 MessageManager類在構造函數內直接創建EmailService類的一個實例,換言之,MessageManager類精確的知道創建和使用了哪種類型的服務。這種耦合表示了代碼的內部鏈接性。一個類知道與其交互的類的大量信息,我們稱為高耦合。這就會增加軟件修改的負擔,因為修改一個類很可能破壞依賴於它的另一個類。
上面的代碼設計還有一個問題:當MessageManager不想采用Email方式發送消息,而是采用其它方式,如微信,那么必須重新實現MessageManager類。
為了降低組件之間的耦合程度,一般采取兩個獨立但相關的步驟:
(1)在兩塊代碼之間引入抽象層。
通常使用接口來代表兩個類之間的抽象層。如實例2所示。
| public interface IMessageService { void SendMessage(); } public class EmailService : IMessageService { public void SendMessage() { Console.WriteLine("通過電子郵件發送消息"); } } public class MessageManager { private IMessageService msgService; public MessageManager() { msgService=new EmailService(); } public void SendMsg() { msgService.SendMessage(); } } |
(2)把選擇抽象實現的責任移到消費者的外部。
需要把EmailService類的創建移到MessageManager類的外面。
把依賴的創建移到使用這些依賴類的外部,這稱為控制反轉模式,之所以這樣命名,是因為反轉的是依賴的創建,正因為如此,才消除了消費者類對依賴創建的控制。
控制反轉(Inversion of Control,即IOC)模式是抽象的:它只是表述應該從消費者類中移除依賴創建,而沒有表述如何實現。
1.2 依賴注入
依賴注入(Dependence Injection,即DI)是一種控制反轉的形式。其意思是自身對象中的內置對象是通過注入的方式進行創建。注入方式通常采用構造函數注入或屬性注入。
1.2.1 構造函數注入
示例3
| public class MessageManager { private IMessageService msgService; public MessageManager(IMessaageService service) { msgService=service; } public void SendMsg() { msgService.SendMessage(); } } |
示例3有一個顯著的優點,它極大地簡化了構造函數的實現。組件總是期望創建它的類能夠傳遞需要的依賴。而它只需要存儲IMessaageService接口的實例以便之后使用,不需要知道它自己的依賴項。另一個有點就是需求的透明性。任何要創建MessageManager類實例的代碼都能查看構造函數,並精確的知道那些內容是使用該類必須的。
1.2.2 屬性注入
顧名思義,該方式是通過設置對象上的公共屬性而不是通過使用構造函數參數來注入依賴的。如示例4所示。
示例4
| public class MessageManager { public IMessageService msgService {get; set;} public void SendMsg() { if (msgService ==null) { Throw new InvalidOperationException(); } msgService.SendMessage(); } } |
上面的兩種注入方式,都需要手動提供所需的依賴項,也就意味着都需要我們知道如何來滿足每一部分的需要。
依賴注入容器是一個使依賴解析變得簡單的一種方式。
1.3 依賴注入框架---Autofac
Autofac是一款IOC容器框架,比較於其他的IOC框架,如Spring.NET,Unity,Castle等等框架,它很輕量級,性能上也很高。
1.3.1 Autofac 的使用
- 獲取Autofac
通過VS中的NuGet來加載AutoFac,引入成功后引用就會出現Autofac。如圖1所示。

圖1-1 獲取Autofac
示例5中,數據層有兩個類,一個是Oracle 一個是SQLSERVER。我們在使用的時候可以選擇調用那個數據庫。通過Autofac來完成構造函數注入。
示例5
| /// <summary> /// 數據源操作接口 /// </summary> public interface IDataSource { /// <summary> /// 獲取數據 /// </summary> /// <returns></returns> string GetData(); } /// <summary> /// SQLSERVER數據庫 /// </summary> public class Sqlserver : IDataSource { public string GetData() { return "通過SQLSERVER獲取數據"; } } /// <summary> /// ORACLE數據庫 /// </summary> public class Oracle : IDataSource { public string GetData() { return "通過Oracle獲取數據"; } } /// <summary> /// 數據源管理類 /// </summary public class DataSourceManager IDataSource _ds; public string GetData()
static void Main(string[] args) { //獲取Autofac容器構造器 var builder = new ContainerBuilder(); // 注冊類型 builder.RegisterType<DataSourceManager>(); // 注冊實例(即將SqlServer類型注冊為IDataSource的實例 builder.RegisterType<Sqlserver>().As<IDataSource>(); // 由構造器創建Ioc容器 using (var container = builder.Build()) { // 解析實例 var manager = container.Resolve<DataSourceManager>(); Console.WriteLine(manager.GetData()); Console.ReadLine(); } } |
示例5所示代碼展示了Autofac的基本使用,在實際開發中,還有很多其它的用法。
(1) AsImplementedInterfaces (注冊多個接口)
在很多情況下,一個類可能實現了多個接口,如果按照實例5的寫法,我們需要為每一個接口都注冊實例,非常繁瑣。這時候就可以使用AsImplementedInterfaces() 方法,可以把一個類注冊給它實現的全部接口。代碼如下:
builder.RegisterType<Sqlserver>().AsImplementedInterfaces()
(2)利用反射注冊
在實際開發中,往往會有很多接口需要注冊,這時我們可以通過反射的方式將其全部注冊。代碼如下:
//加載實現類的程序集
Assembly asm = Assembly.Load("DAL.BLL");
builder.RegisterAssemblyTypes(asm).AsImplementedInterfaces();
我們可以在配置文件中將程序集信息在<appSettings>節點中進行配置,來減少對程序集的引用,徹底解除耦合。
(3) PropertiesAutowired (利用屬性注入)
上述通過Autofac進行注入都是針對構造函數進行的注冊,如果使用屬性進行注冊需要使用PropertiesAutowired()方法。
//加載實現類的程序集
Assembly asm = Assembly.Load("DAL.BLL");
builder.RegisterAssemblyTypes(asm).AsImplementedInterfaces().PropertiesAutowired();
(4)一個接口多個實現
一個接口有多個實現類的情況,如實例5中,SqlServer 和 Oracle 都實現了IDataSource接口,若有同時注冊,需要使用Resolve<IEnumerable<IAnimalBLL>>()即可。如實例6所示。
實例6
| Assembly asm = Assembly.Load("DAL.BLL"); builder.RegisterAssemblyTypes(asm).AsImplementedInterfaces(); using (var container = builder.Build()) { IEnumerable<IDataSource> dals = container.Resolve<IEnumerable<IDataSource>>(); foreach(var dal in dals) { Console.WriteLine(dal.GetData()); } Console.ReadLine(); }
|
也可以通過Named()方法在指定要想獲取的類型。如示例7所示。
示例7
| //獲取Autofac容器構造器 var builder = new ContainerBuilder(); // 注冊類型 builder.RegisterType<DataSourceManager>(); // 注冊實例 builder.RegisterType<Sqlserver>().Named<IDataSource>("SqlServer"); builder.RegisterType<Sqlserver>().Named<IDataSource>("Oracle"); // 由構造器創建Ioc容器 using (var container = builder.Build()) { // 解析實例 var manager = container.ResolveNamed<DataSourceManager>("SqlServer"); Console.WriteLine(manager.GetData()); Console.ReadLine(); } |
1.3.2 通過配置方式使用Autofac
通過配置實現Autofac 需要添加對Autofac.Configuration.dll的引用。
示例8
| 配置文件 <configuration> <configSections> <section name="autofac" type="Autofac.Configuration.SectionHandler,Autofac.Configuration"></section> </configSections> <autofac defaultAssembly="AutoFacDemo"> <components> <component type="AutoFacDemo.Model.Oracle,AutoFacDemo" service="AutoFacDemo.Model.IDataSource" /> </components> </autofac> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> </configuration>
C#代碼 var builder = new ContainerBuilder(); builder.RegisterType<DataSourceManager>(); builder.RegisterModule( new ConfigurationSettingsReader("autofac" ));
|
1.4 在Asp.net Mvc中使用Autofac
在.net Mvc中使用Autofac,原理和上述講解一樣,區別在於需要通過Nuget多安裝一個程序集 Autofac.Mvc5
步驟1
在MVC項目中添加對 Autofac 和 Autofac.Mvc5 的引用。
步驟2
創建配置類。在此我們通過屬性注入的方式進行注入,如示例9所示。
示例9
| public class AutofacConfig { public static void Register() { var builder = new ContainerBuilder(); //加載當前程序集 Assembly controllerAss = Assembly.GetExecutingAssembly(); //Assembly controllerAss = Assembly.Load("OA.Web"); //注冊所有的Controller builder.RegisterControllers(controllerAss).PropertiesAutowired();
//注冊數據訪問層程序集 Assembly resAss = Assembly.Load("OA.DAL"); builder.RegisterTypes(resAss.GetTypes()).AsImplementedInterfaces(); //注冊業務層程序集 Assembly bllAss = Assembly.Load("OA.BLL"); builder.RegisterTypes(bllAss.GetTypes()).AsImplementedInterfaces();
var container = builder.Build(); //當mvc創建controller對象的時候,都是由AutoFac為我們創建 //Controller對象 DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); } } |
示例9中對數據層和業務層的注入請根據實際情況進行注冊,不是必須的。
步驟3
在Global.asax 的Application_Start() 注冊自己的控制器類
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
//Autofac配置
AutofacConfig.Register();
}
至此,在MVC中使用 Autofac 的配置步驟完成。
在Controller中的使用如下:
public class HomeController :Controller
{
public IDataSource dataSource { get ; set; }
public ActionResult Index( )
{
var data = dataSource.GetData( );
return View(data);
}
}
dataSource 屬性會被 Autofac 自動實例化。
