zoukankan      html  css  js  c++  java
  • DI容器Ninject在管理接口和实现、基类和派生类并实现依赖注入方面的实例

    当一个类依赖于另一个具体类的时候,这样很容易形成两者间的"强耦合"关系。我们通常根据具体类抽象出一个接口,然后让类来依赖这个接口,这样就形成了"松耦合"关系,有利于应用程序的扩展。我们可以用DI容器、Dependency Injection容器,即依赖注入容器来管理接口和实现类。所谓的"依赖注入"是指:当某个类需要用到或依赖于某个接口类的实现类时,通过DI容器的API把接口注入到该类的构造函数或属性上,接着调用注入接口的方法,DI容器根据已经注册的依赖性链,要么自动执行接口实现类的方法,要么使用它的API选择性地执行某个接口实现类的方法。本篇体验使用Ninject这个DI容器。

    本篇内容包括:

      Ninject管理简单的接口和实现类,自动执行接口实现类方法

    需求:一个购物车类计算所有产品的总价

    有关产品:

    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方法实现:

    public class LinqValueCalculator : IValueCalculator
        {
            public decimal ValueProducts(params Product[] products)
            {
                return products.Sum(p => p.Price);
            }
        }

    最后在购物车类中,通过其构造函数把IValueCalculator注入进来:

    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容器自动帮我们找到该接口的实现类并调用方法。

      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框架(第三版)

  • 相关阅读:
    JavaScript访问ab页面定时跳转代码
    http协议相关-待续
    curl发送get和post请求
    Java入门——动态网页jsp(jdk下载和配置环境变量)
    LeetCode:Best Time to Buy and Sell Stock
    LeetCode:Reverse Integer
    LeetCode:Same Tree
    LeetCode:Single Number II
    LeetCode:Single Number
    LeetCode:Minimum Depth of Binary Tree
  • 原文地址:https://www.cnblogs.com/darrenji/p/3801520.html
Copyright © 2011-2022 走看看