DI容器Ninject在管理接口和實現、基類和派生類並實現依賴注入方面的實例

當一個類依賴於另外一個具體類的時候,這樣很容易造成二者間的"強耦合"關係。咱們一般根據具體類抽象出一個接口,而後讓類來依賴這個接口,這樣就造成了"鬆耦合"關係,有利於應用程序的擴展。咱們能夠用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框架(第三版)

相關文章
相關標籤/搜索