磕磕碰碰才是人生

博客园 首页 新随笔 联系 订阅 管理

以下为DI控制反转个人理解烦请各位大牛指教~

编写程序时我们应当遵循抵耦合高内聚的原则(各个功能模块互不依赖).

我们可以利用面向对象里面接口的特性来进行DI控制反转,让功能模块全部依赖接口,而不依赖具体的实现类,当程序跑起来以后通过注入的方式注入具体的实现类如以下代码:

  /// <summary>
    /// 购物车类
    /// </summary>
    public class ShoppingCart {
        /// <summary>
        /// 创建计算器接口
        /// </summary>
        IvalueCalculator calculator;


        /// <summary>
        /// 构造函数来注入实际调用的计算方法
        /// </summary>
        /// <param name="ivalueCalculator"></param>
        public ShoppingCart(IvalueCalculator ivalueCalculator)
        {
            calculator = ivalueCalculator;
        }

        /// <summary>
        /// 价格计算
        /// </summary>
        /// <returns></returns>
        public decimal CalculateStockValue()
        {
            Product[] products = {
                new Product {Name = "西瓜", Category = "水果", Price = 2.3M},
              new Product {Name = "苹果", Category = "水果", Price = 4.9M},
              new Product {Name = "空心菜", Category = "蔬菜", Price = 2.2M},
              new Product {Name = "地瓜", Category = "蔬菜", Price = 1.9M}
            };
            decimal totalValue = calculator.ValueProducts(products);
            return totalValue;
        }


    }


    /// <summary>
    /// 计算器实现类
    /// </summary>
    public class LinqValueCalculator : IvalueCalculator
    {
        /// <summary>
        /// 价格计算实现方法
        /// </summary>
        /// <param name="products"></param>
        /// <returns></returns>
        public decimal ValueProducts(params Product[] products)
        {
            return products.Sum(u => u.Price);
        }


    }

    /// <summary>
    /// 计算器接口
    /// </summary>
    public interface IvalueCalculator
    {
        /// <summary>
        /// 价格计算方法
        /// </summary>
        /// <param name="products"></param>
        /// <returns></returns>
        decimal ValueProducts(params Product[] products);
    }

 

这样,购物车类就实现了松耦合,购物车内的计算价格方法只依赖于计算器接口(IvalueCalculator ),而不依赖具体的计算类(LinqValueCalculator),实际的价格计算类我们通过构造函数的方法注入到购物车内的计算器接口

 

当我们在实际使用时既可以像如下方法一样实现

static void Main(string[] args)
        {
               //创建一个接口的对象,引用计算类
            IvalueCalculator calculator = new LinqValueCalculator();
            //以方法传入具体实现类
            ShoppingCart shopping = new ShoppingCart(calculator);
        //调用
            Console.WriteLine("价格:{0}", shopping.CalculateStockValue());
            Console.ReadLine();
        }

 

 

可以通过C#的 Ninject 来管理各种注入,只需要提前绑定好接口的对应实现类既可以在使用时去索要一个对应的实现类,如下代码

     //Ninject
            IKernel ninjectKernel = new StandardKernel();
            //把一个接口(IValueCalculator)绑定到一个实现该接口的类(LinqValueCalculator)
            ninjectKernel.Bind<IvalueCalculator>().To<LinqValueCalculator>();
            
            //向NiNject索要一个IvalueCalculator的实现类
            IvalueCalculator calcImpl = ninjectKernel.Get<IvalueCalculator>();
            //注入购物车类
            ShoppingCart shopping = new ShoppingCart(calcImpl);

            Console.WriteLine("价格:{0}", shopping.CalculateStockValue());
            Console.ReadLine();

 

 

知识点1:并且,如果你的价格计算实现类(LinqValueCalculator)的内部调用了其它接口,那么Ninject会自动帮你注入要调用接口的实现类(前提是这个调用的接口在之前已经绑定了实现类) 

例如:创建一个打折接口(IDiscountHelper),再创建一个类来实现一个默认打折方法(DefaultDiscountHelper)

 

    /// <summary>
    /// 折扣计算接口
    /// </summary>
    public interface IDiscountHelper {
        decimal ApplyDiscount(decimal totalParam);
    }

    /// <summary>
    /// 默认折扣计算接口
    /// </summary>
    public class DefaultDiscountHelper : IDiscountHelper
    {
        /// <summary>
        /// 折扣结算方法
        /// </summary>
        /// <param name="totalParam"></param>
        /// <returns></returns>
        public decimal ApplyDiscount(decimal totalParam)
        {
           return (totalParam - (1m / 10m * totalParam));
        }
    }

 

在价格计算实现类(LinqValueCalculator)内调用打折接口(IDiscountHelper)

    /// <summary>
    /// 计算器实现类
    /// </summary>
    public class LinqValueCalculator : IvalueCalculator
    {
        /// <summary>
        /// 定义一个打折接口
        /// </summary>
        private IDiscountHelper discount;

        /// <summary>
        /// 用构造函数注入打折类
        /// </summary>
        /// <param name="discountHelper"></param>
        public LinqValueCalculator(IDiscountHelper discountHelper) {
            discount = discountHelper;
        }

        /// <summary>
        /// 价格计算实现方法
        /// </summary>
        /// <param name="products"></param>
        /// <returns></returns>
        public decimal ValueProducts(params Product[] products)
        {
            //调用打折接口的实现类 来计算价格
            return discount.ApplyDiscount(products.Sum(u => u.Price));
        }


    }

 

那么当我们在调用价格计算类(LinqValueCalculator)时,Ninject会自动帮我们注入打折实现类(DefaultDiscountHelper)

(注:前提是,打折接口(IDiscountHelper)你在之前已经用Ninject绑定了它的实现类打折实现类(DefaultDiscountHelper))

 //Ninject
            IKernel ninjectKernel = new StandardKernel();
            //把一个接口(IValueCalculator)绑定到一个实现该接口的类(LinqValueCalculator)
            ninjectKernel.Bind<IvalueCalculator>().To<LinqValueCalculator>();

            //给折扣接口绑定一个默认的打折类
            ninjectKernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>();

            //向NiNject索要一个IvalueCalculator的实现类
            IvalueCalculator calcImpl = ninjectKernel.Get<IvalueCalculator>();

            //注意:价格计算类(LinqValueCalculator)类里面有一个折扣接口(IDiscountHelper),折扣接口需要一个具体的折扣类(DefaultDiscountHelper),
            //我这里在向 ninjectKernel.Get<IvalueCalculator>()  请求价格计算类(LinqValueCalculator类里面有一个折扣接口)的时候并没有传入折扣实现类
            //是ninject自己帮我完成了.
            //原理就是在上面我们给打折接口(IDiscountHelper)绑定了一个默认的打折实现类(DefaultDiscountHelper),ninject检测到我们绑定了默认打折实现类(DefaultDiscountHelper),所以自动邦我们补全了;

            //注入购物车类
            ShoppingCart shopping = new ShoppingCart(calcImpl);

            Console.WriteLine("价格:{0}", shopping.CalculateStockValue());
            Console.ReadLine();

 

 

知识点2:现在我们的打折实现类(DefaultDiscountHelper)内的折扣力度是写死的,如果我们想让折扣力度由外部传入该肿么办呢?

    /// <summary>
    /// 默认折扣计算接口
    /// </summary>
    public class DefaultDiscountHelper : IDiscountHelper
    {
        /// <summary>
        /// 默认折扣类的打折力度
        /// </summary>
        public decimal DiscountSize { get; set; }

        /// <summary>
        /// 折扣结算方法
        /// </summary>
        /// <param name="totalParam"></param>
        /// <returns></returns>
        public decimal ApplyDiscount(decimal totalParam)
        {
           return (totalParam - (DiscountSize / 10m * totalParam));
        }
    }

 

在NinJect注入具体实现类时,我们可以通过下面的形式对打折力度进去传入

  Ninject核心对象.Bind<绑定的接口>().To<要绑定的实现类>().WithPropertyValue("实现类内部属性的名称",实现类内部属性的值);

 

        //Ninject
            IKernel ninjectKernel = new StandardKernel();
            //把一个接口(IValueCalculator)绑定到一个实现该接口的类(LinqValueCalculator)
            ninjectKernel.Bind<IvalueCalculator>().To<LinqValueCalculator>();

            //给折扣接口绑定一个默认的打折类
            ninjectKernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize", 5M);

            //向NiNject索要一个IvalueCalculator的实现类
            IvalueCalculator calcImpl = ninjectKernel.Get<IvalueCalculator>();

        .......

如果要给多个属性赋值,则可以在Bind和To方式后添加多个WithPropertyValue(<属性名>,<属性值>)方法。

 

同样的我们也可以以形参的形式传入

    /// <summary>
    /// 默认折扣计算接口
    /// </summary>
    public class DefaultDiscountHelper : IDiscountHelper
    {
        public DefaultDiscountHelper(decimal discountSize)
        {
            DiscountSize = discountSize;
        }

        /// <summary>
        /// 默认折扣类的打折力度
        /// </summary>
        public decimal DiscountSize { get; set; }

        /// <summary>
        /// 折扣结算方法
        /// </summary>
        /// <param name="totalParam"></param>
        /// <returns></returns>
        public decimal ApplyDiscount(decimal totalParam)
        {
           return (totalParam - (DiscountSize / 10m * totalParam));
        }
    }

 

此时在注入实现类时改为

  Ninject核心对象.Bind<绑定的接口>().To<要绑定的实现类>().WithConstructorArgument("形参的名称",形参的值);

 

调用方法改为

  //把一个接口(IValueCalculator)绑定到一个实现该接口的类(LinqValueCalculator)
            ninjectKernel.Bind<IvalueCalculator>().To<LinqValueCalculator>();

            //给折扣接口绑定一个默认的打折类
            ninjectKernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("discountSize",10M);

            //向NiNject索要一个IvalueCalculator的实现类
            IvalueCalculator calcImpl = ninjectKernel.Get<IvalueCalculator>();

 

如果构造函数存在多个形参那么在每一个后面在.WithConstructorArgument 即可

例如:

  Ninject核心对象.Bind<绑定的接口>().To<要绑定的实现类>().WithConstructorArgument("形参的名称",形参的值).WithConstructorArgument("形参的名称1",形参的值1).WithConstructorArgument("形参的名称2",形参的值2);

 

 

 

知识点3:在对接口进行绑定时我们都是通过如下方法绑定

 

  //向NiNject索要一个IvalueCalculator的实现类
            IvalueCalculator calcImpl = ninjectKernel.Get<IvalueCalculator>();

            //把要到的实现类注入到购物车类内部
            ShoppingCart shopping = new ShoppingCart(calcImpl);

转换为中文意为:

由计算(IvalueCalculator) 接口 calcImpl  向  ninjectKernel 索要一个(计算接口)IvalueCalculator的具体实现类(LinqValueCalculator)(具体实现类由上面接口绑定而得)的引用;

创建一个(购物车类)ShoppingCart shopping ,指向(购物车)ShoppingCart类并且由构造函数注入前面从ninjectKernel获得的计算接口(IvalueCalculator)的实现类(LinqValueCalculator);

 

 

这样写我个人觉得最通俗易懂,但是不够简洁,Ninject还有另外一种更简单的获取实现类的方法

            ninjectKernel.Bind<IvalueCalculator>().To<LinqValueCalculator>();
            //给折扣接口绑定一个默认的打折类
            ninjectKernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("discountSize",5M);//标识自我绑定
            ninjectKernel.Bind<ShoppingCart>().ToSelf();
            //注入购物车类
            ShoppingCart shopping = ninjectKernel.Get<ShoppingCart>();

 

由知识点1可知: Ninject 不仅可以 以  接口 绑定 实现类 的 形式 进行 绑定,也可以进行具体类绑定具体类的操作;

这是自我绑定方法,再由上方知识点可推断  当我们要调用的类需要一个接口的实现类时,如果这个接口是在之前已经进行绑定过的,那么Ninject会自动邦我们进行注入

也就是说:

购物车(ShoppingCart)类先进行绑定,它绑定的实现类就是他自己,所有通过Ninject请求购物车(ShoppingCart)类的请求都会返回一个购物车(ShoppingCart)类自己,再因为 已经在Ninject绑定的接口在被调用时都会自动邦我们进行注入操作

(购物车类内部有一个计算接口(IvalueCalculator)计算接口需要一个计算实现类(LinqValueCalculator)  而计算接口在之前我们已经进行了绑定,所以Ninject会自动帮我们进行注入)  

 

最核心的就是要明白,Ninject会对所有已经绑定过的接口进行自动注入实现类的操作.(貌似有一定的递归逻辑?)

这样整个自绑定逻辑就已经缕清楚了~

 

 

 

 

由"Liam Wang"编写的"[ASP.NET MVC 小牛之路]04 - 依赖注入(DI)和Ninject"整理而成

URL:https://www.cnblogs.com/willick/p/3223042.html

posted on 2019-01-15 11:08  磕磕碰碰才是人生  阅读(494)  评论(0编辑  收藏  举报