当一个类依赖于另一个具体类的时候,这样很容易形成两者间的"强耦合"关系。我们通常根据具体类抽象出一个接口,然后让类来依赖这个接口,这样就形成了"松耦合"关系,有利于应用程序的扩展。我们可以用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 = , Name = "Product1", Price = 85M},
new Product(){Id = , 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()); //
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()); //
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()); //
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()); //
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 = , Name = "Product1", Price = 85M},
new Product(){Id = , 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()); //
Console.ReadKey();
}
}

结果显示:42.5

Ninject条件绑定

即一个接口可以有多个实现,并使用Ninject的API规定在某些条件下使用某些接口的实现类。

比如,给计算价格的接口再添加一个通过遍历组合计算价格的类:

public class IterativeValueCalculator : IValueCalculator
{
public decimal ValueProducts(params Product[] products)
{
decimal result = ;
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()); //
Console.ReadKey();
}
}

结果显示:85

总结

Ninject这个DI容器,可以帮助我们管理接口和实现,基类和派生类,并提供了一些API,允许我们为接口的实现类或基类的派生类设置构造函数参数初始值、设置属性的初始值,甚至设置在某种条件使用特定的实现,即条件绑定。

参考资料:
精通ASP.NET MVC3框架(第三版)

DI容器Ninject在管理接口和实现、基类和派生类并实现依赖注入方面的实例的更多相关文章

  1. 接上一篇:(四) 控制反转(IOC)/ 依赖注入(DI)

    spring 核心功能:beans.core.context.expression Spring设计理念 Spring是面向Bean的编程 Spring三个核心组件(Core.Context.Bean ...

  2. Yii2.0 依赖注入(DI)和依赖注入容器的原理

    依赖注入和依赖注入容器 为了降低代码耦合程度,提高项目的可维护性,Yii采用多许多当下最流行又相对成熟的设计模式,包括了依赖注入(Denpdency Injection, DI)和服务定位器(Serv ...

  3. ASP.NET 5 牛刀小試(二):加入第三方 DI 容器

    上回介绍了 ASP.NET vNext 自带容器的基本用法,这次要试试把 ASP.NET vNext 的自带容器换成 Autofac. 这一次,在编写范例程序的过程中,光是解决 KRE 与相关套件的版 ...

  4. ASP.NET Core Web 应用程序系列(一)- 使用ASP.NET Core内置的IoC容器DI进行批量依赖注入(MVC当中应用)

    在正式进入主题之前我们来看下几个概念: 一.依赖倒置 依赖倒置是编程五大原则之一,即: 1.上层模块不应该依赖于下层模块,它们共同依赖于一个抽象. 2.抽象不能依赖于具体,具体依赖于抽象. 其中上层就 ...

  5. laravel服务容器(IOC控制反转,DI依赖注入),服务提供者,门脸模式

    laravel的核心思想: 服务容器: 容器:就是装东西的,laravel就是一个个的对象 放入:叫绑定 拿出:解析 使用容器的目的:这里面讲到的是IOC控制反转,主要是靠第三方来处理具体依赖关系的解 ...

  6. 控制反转(IoC)与依赖注入(DI)

    前言 最近在学习Spring框架,它的核心就是IoC容器.要掌握Spring框架,就必须要理解控制反转的思想以及依赖注入的实现方式.下面,我们将围绕下面几个问题来探讨控制反转与依赖注入的关系以及在Sp ...

  7. yii依赖注入和依赖注入容器

    依赖注入和依赖注入容器¶ 为了降低代码耦合程度,提高项目的可维护性,Yii采用多许多当下最流行又相对成熟的设计模式,包括了依赖注入(Denpdency Injection, DI)和服务定位器(Ser ...

  8. 依赖注入(DI)在PHP中的实现

    什么是依赖注入? IOC:英文全称:Inversion of Control,中文名称:控制反转,它还有个名字叫依赖注入(Dependency Injection,简称DI). 当一个类的实例需要另一 ...

  9. spring(3)------控制反转(IOC)/依赖注入(DI)

    一.spring核心概念理解 控制反转: 控制反转即IoC (Inversion of Control).它把传统上由程序代码直接操控的对象的调用权交给容器.通过容器来实现对象组件的装配和管理. 所谓 ...

随机推荐

  1. AdvStringGrid 滚动条问题

    1.默认水平方向 滚动条是 小的 滚动的时候 数据会随着滚动 而 滚动的. 2.默认垂直方向 滚动条是 小的 滚动的时候 数据不会随着滚动 而滚动的.ScrollSynch := True; 垂直方向 ...

  2. c 语言文本文件判断是否到达结尾的问题

    在c语言中,判断文件结尾有两种方法,第一种是使用feof()函数,feof(fp)用于测试fp所指向的文件的当前状态是否为“文件结束”.如果是,函数则返回的是非0值(真),否则为0(假),要注意的是, ...

  3. Mybatis入门及Dao开发方式

    本节内容: Mybatis介绍 使用jdbc编程问题总结 Mybatis架构 Mybatis入门程序 Dao的开发方式 SqlMapConfig.xml文件说明 一.Mybatis介绍 MyBatis ...

  4. XeLaTeX下如何以原大小显示PNG

    在XeLaTeX里直接使用\includegraphics{test.png}这样的命令引入PNG,可能会发现图片直接被缩放到占满文档宽度,这是因为PNG这种bitmap类型的图片里通常不会带上met ...

  5. CCF CSP 201403-2 窗口

    CCF计算机职业资格认证考试题解系列文章为meelo原创,请务必以链接形式注明本文地址 CCF CSP 201403-2 窗口 问题描述 在某图形操作系统中,有 N 个窗口,每个窗口都是一个两边与坐标 ...

  6. c++模板与泛型编程基础

    (1)定义函数模板(function template) 函数模板是一个独立于类型的函数,可以产生函数的特定类型版本. // implement strcmp-like generic compare ...

  7. 在android studio中集成javah, ndk-build进行JNI开发

    最近在搞一个android上控制LED灯闪烁的功能,用到了串口编程,搜索了一下,发现Google发布了一个demo,android-serialport-api.有现成的代码和APK,要想自己改JNI ...

  8. [js]变量与数据类型篇

    一.变量 在JavaScript中就用一个变量名表示变量,变量名是大小写英文.数字.$和_的组合,不能用数字开头.变量名也不能是JavaScript的关键字: 1.变量的声明 (1)var:申明一个变 ...

  9. MySQL 关于索引那点事

    索引 其实数据库中的数据是按页存放的其实索引也是按页存放的所以本质上索引也占硬盘空间(以最小的消耗,换取最大的利益) 索引是一种有效组合数据的方式!为快速查找到指定记录做铺垫 目的就是快速或者某个记录 ...

  10. php操作mongodb or查询这样写!

    $where['$or'] = [ ['id' => ['lt'=>0]], ['id2' => ['lt'=>1]] ]; 这个是查询 id>0 或者id2>1的 ...