当一个类依赖于另外一个具体类的时候,这样很容易造成二者间的"强耦合"关系。咱们一般根据具体类抽象出一个接口,而后让类来依赖这个接口,这样就造成了"松耦合"关系,有利于应用程序的扩展。咱们能够用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框架(第三版)