當一個類依賴於另外一個具體類的時候,這樣很容易造成二者間的"強耦合"關係。咱們一般根據具體類抽象出一個接口,而後讓類來依賴這個接口,這樣就造成了"鬆耦合"關係,有利於應用程序的擴展。咱們能夠用DI容器、Dependency Injection容器,即依賴注入容器來管理接口和實現類。所謂的"依賴注入"是指:當某個類須要用到或依賴於某個接口類的實現類時,經過DI容器的API把接口注入到該類的構造函數或屬性上,接着調用注入接口的方法,DI容器根據已經註冊的依賴性鏈,要麼自動執行接口實現類的方法,要麼使用它的API選擇性地執行某個接口實現類的方法。本篇體驗使用Ninject這個DI容器。框架
本篇內容包括:ide
Ninject管理簡單的接口和實現類,自動執行接口實現類方法函數
需求:一個購物車類計算全部產品的總價this
有關產品:spa
public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } }
須要一個計算產品總價的幫助類,不過考慮到擴展性,先寫一個計算產品總價的接口:設計
public interface IValueCalculator { decimal ValueProducts(params Product[] products); }
而後再寫這個計算總價接口的實現類,用Linq方法實現:code
public class LinqValueCalculator : IValueCalculator { public decimal ValueProducts(params Product[] products) { return products.Sum(p => p.Price); } }
最後在購物車類中,經過其構造函數把IValueCalculator注入進來:blog
public class ShoppingCart { private IValueCalculator calculator; public ShoppingCart(IValueCalculator calc) { calculator = calc; } public decimal TotalValue() { Product[] products = { new Product(){Id = 1, Name = "Product1", Price = 85M}, new Product(){Id = 2, Name = "Product2", Price = 90M} }; return calculator.ValueProducts(products); } }
使用NuGet安裝Ninject,客戶端調用時,首先要註冊接口和實現類的依賴鏈,在須要用到接口的時候,再從DI容器中把這個接口取出來。接口
class Program { static void Main(string[] args) { IKernel ninjectKernel = new StandardKernel(); ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); IValueCalculator calculator = ninjectKernel.Get<IValueCalculator>(); ShoppingCart cart = new ShoppingCart(calculator); Console.WriteLine("總價:{0}", cart.TotalValue()); Console.ReadKey(); } }
結果顯示:175
從中能夠看到,咱們只是把接口注入到ShoppingCart類中,在調用接口方法的時候,DI容器自動幫咱們找到該接口的實現類並調用方法。ci
Ninject管理嵌套接口和實現類,自動執行接口實現類方法
需求:一個購物車類計算全部產品的總價,並打折
分析:計算全部產品總價的時候,ShoppingCart類依賴於計算總價的類,而但須要打折的時候,這個計算總價的實現類一樣須要依賴於一個打折接口。不管依賴關係如何嵌套,咱們只要把接口和實現類交給Ninject,其他事情Ninject輕鬆搞定。
打折的方式可能有不少種,這裏也是一個擴展點,因此先打折接口:
public interface IDiscountHelper { decimal ApplyDiscount(decimal total); }
默認打9折:
public class DefaultDiscountHelper : IDiscountHelper { public decimal ApplyDiscount(decimal total) { return (total - 10m/100m*total); } }
計算總價的實現類,如今須要依賴於這個打折接口:
public class LinqValueCalculator : IValueCalculator { private IDiscountHelper discounter; public LinqValueCalculator(IDiscountHelper discountParam) { this.discounter = discountParam; } public decimal ValueProducts(params Product[] products) { return discounter.ApplyDiscount(products.Sum(p => p.Price)); } }
客戶端如今須要註冊打折接口和實現類的關係:
class Program { static void Main(string[] args) { IKernel ninjectKernel = new StandardKernel(); ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); ninjectKernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>(); IValueCalculator calculator = ninjectKernel.Get<IValueCalculator>(); ShoppingCart cart = new ShoppingCart(calculator); Console.WriteLine("總價:{0}", cart.TotalValue()); //175 Console.ReadKey(); } }
結果顯示:157.5
可見,一旦在Ninject中註冊好全部的接口和實現類關係,在調用計算總價接口方法時,Ninject自動爲咱們找到計算總價的實現類,接着自動找到打折的實現類。
Ninject設置接口實現類屬性值
需求:一個購物車類計算全部產品的總價,並打折,打折的金額能夠動態設置
分析:打折實現類添加一個屬性,Ninject註冊接口和實現類的時候,給該屬性賦初值
在打折接口實現類中添加一個屬性:
public class DefaultDiscountHelper : IDiscountHelper { public decimal DiscountSize { get; set; } public decimal ApplyDiscount(decimal total) { return (total - DiscountSize/100m*total); } }
客戶端應用程序中,在註冊接口和實現類的時候爲DiscountSize賦初值。
class Program { static void Main(string[] args) { IKernel ninjectKernel = new StandardKernel(); ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); ninjectKernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize",50M); IValueCalculator calculator = ninjectKernel.Get<IValueCalculator>(); ShoppingCart cart = new ShoppingCart(calculator); Console.WriteLine("總價:{0}", cart.TotalValue()); //175 Console.ReadKey(); } }
結果顯示:87.5
Ninject設置接口實現類的構造參數值
依然是這樣的需求:一個購物車類計算全部產品的總價,並打折,打折的金額能夠動態設置
在打折接口實現類中添加構造函數和私有變量:
public class DefaultDiscountHelper : IDiscountHelper { private decimal discountRate; public DefaultDiscountHelper(decimal discountParam) { discountRate = discountParam; } public decimal ApplyDiscount(decimal total) { return (total - discountRate/100m*total); } }
客戶端應用程序中,在註冊接口和實現類的時候爲構造函數參數賦初值:
class Program { static void Main(string[] args) { IKernel ninjectKernel = new StandardKernel(); ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); ninjectKernel.Bind<IDiscountHelper>() .To<DefaultDiscountHelper>() .WithConstructorArgument("discountParam", 50M); IValueCalculator calculator = ninjectKernel.Get<IValueCalculator>(); ShoppingCart cart = new ShoppingCart(calculator); Console.WriteLine("總價:{0}", cart.TotalValue()); //175 Console.ReadKey(); } }
結果顯示:87.5
Ninject具體類的自身綁定
Ninject具體類的自身綁定,從文字上看,彷佛有點不知所云。咱們能夠這樣理解:
當Ninject註冊接口和實現類的時候,咱們能夠經過IValueCalculator calculator = ninjectKernel.Get<IValueCalculator>()拿到接口。ShoppingCart正是依賴於IValueCalculator,雖然ShoppingCart不是接口類型,可是否能夠經過某種設置,讓咱們也能夠經過ShoppingCart cart = ninjectKernel.Get<ShoppingCart>()這種方式拿到ShoppingCart這個具體類的實例呢?
答案是:能夠的。咱們須要讓ShoppingCart這個具體在Ninject實現自身綁定:
class Program { static void Main(string[] args) { IKernel ninjectKernel = new StandardKernel(); ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); ninjectKernel.Bind<IDiscountHelper>() .To<DefaultDiscountHelper>() .WithConstructorArgument("discountParam", 50M); ninjectKernel.Bind<ShoppingCart>().ToSelf(); ShoppingCart cart = ninjectKernel.Get<ShoppingCart>(); Console.WriteLine("總價:{0}", cart.TotalValue()); //175 Console.ReadKey(); } }
結果顯示:87.5
Ninject綁定基類和派生類,並設置派生類的屬性值
新的需求是這樣:一個購物車類計算全部產品的總價,並打折,打折的金額能夠動態設置;給被計算產品的價格設置一個上限。
分析:能夠把ShoppingCar設計爲基類,並把其中計算總價的方法設計爲virtual,在ShoppingCart的派生類中添加一個有關價格上限的屬性,再重寫ShoppingCart這個基類中的計算總價方法,把產品價格上限這個因素考慮進去。
把ShoppingCart做爲基類,把計算總價的方法設置爲virtual方法:
public class ShoppingCart { protected IValueCalculator calculator; protected Product[] products; public ShoppingCart(IValueCalculator calc) { calculator = calc; products = new[] { new Product(){Id = 1, Name = "Product1", Price = 85M}, new Product(){Id = 2, Name = "Product2", Price = 90M} }; } public virtual decimal TotalValue() { return calculator.ValueProducts(products); } }
ShoppingCart的派生類重寫計算總價的方法並考慮價格上限:
public class LimitPriceShoppingCart : ShoppingCart { public LimitPriceShoppingCart(IValueCalculator calc) : base(calc){} public override decimal TotalValue() { var filteredProducts = products.Where(e => e.Price < PriceLimit); return calculator.ValueProducts(filteredProducts.ToArray()); } public decimal PriceLimit { get; set; } }
基類和派生類註冊到Ninject中,併爲派生類的屬性賦初始值:
class Program { static void Main(string[] args) { IKernel ninjectKernel = new StandardKernel(); ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); ninjectKernel.Bind<IDiscountHelper>() .To<DefaultDiscountHelper>() .WithConstructorArgument("discountParam", 50M); ninjectKernel.Bind<ShoppingCart>().To<LimitPriceShoppingCart>().WithPropertyValue("PriceLimit", 88M); ShoppingCart cart = ninjectKernel.Get<ShoppingCart>(); Console.WriteLine("總價:{0}", cart.TotalValue()); //175 Console.ReadKey(); } }
結果顯示:42.5
Ninject條件綁定
即一個接口能夠有多個實現,並使用Ninject的API規定在某些條件下使用某些接口的實現類。
好比,給計算價格的接口再添加一個經過遍歷組合計算價格的類:
public class IterativeValueCalculator : IValueCalculator { public decimal ValueProducts(params Product[] products) { decimal result = 0; foreach (var item in products) { result += item.Price; } return result; } }
並規定注入到LimitPriceShoppingCart的時候使用這個計算總價的實現:
class Program { static void Main(string[] args) { IKernel ninjectKernel = new StandardKernel(); ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); ninjectKernel.Bind<IValueCalculator>() .To<IterativeValueCalculator>() .WhenInjectedInto<LimitPriceShoppingCart>(); ninjectKernel.Bind<IDiscountHelper>() .To<DefaultDiscountHelper>() .WithConstructorArgument("discountParam", 50M); ninjectKernel.Bind<ShoppingCart>().To<LimitPriceShoppingCart>().WithPropertyValue("PriceLimit", 88M); ShoppingCart cart = ninjectKernel.Get<ShoppingCart>(); Console.WriteLine("總價:{0}", cart.TotalValue()); //175 Console.ReadKey(); } }
結果顯示:85
總結
Ninject這個DI容器,能夠幫助咱們管理接口和實現,基類和派生類,並提供了一些API,容許咱們爲接口的實現類或基類的派生類設置構造函數參數初始值、設置屬性的初始值,甚至設置在某種條件使用特定的實現,即條件綁定。
參考資料:精通ASP.NET MVC3框架(第三版)