.NET MVC 依赖注入

.NET MVC 依赖注入

当一个组件依赖于其他组件时,我们称其为耦合(coupling)。一个类知道与其交互的类的大量信息,我们称其为高耦合。

在软件设计过程中,高耦合通常认为是软件设计的责任。当一个类精确地知道另一个类的设计和实现时,就会增加软件修改的负担,因为修改一个类很有可能破坏依赖于它的另一个类。

为降低组件之间的耦合程度,一般采取两个独立但相关的步骤:
(1) 在两块代码之间引入抽象层。
(2) 把选择抽象实现的责任移到消费者类的外部。

控制反转(IoC)模式是抽象的;用控制反转模式实现责任转移的两种常用方法:服务定位器和依赖注入。

1. 服务定位器

服务定位器模式是控制反转模式的一种实现方式,它通过一个称为服务定位器的外部组件来为需要依赖的组件提供依赖。服务定位器有时是一个具体的接口,为特定服务提供强类型的请求;有时它又可能是一个泛型类型,可以提供任意类型的请求服务。

1.1 强类型服务定位器

public interface IServiceLocator
{
		IMessagingService GetMessageingService();
}

在本例中,当需要一个实现了IMessagingService接口的对象时,我们知道应该调用GetMessagingService方法。该方法返回一个IMessagingService接口对象,因此,我们不需要转换结果的类型。

上面的示例是把服务定位器作为一个接口,而不是一个具体类型。我们的目标是降低组件之间的耦合程度,其中包括消费者代码和服务定位器之间的耦合。如果消费者代码实现了IServiceLocator接口,就可以在运行时环境中选择合适的实现方式。

用强类型服务定位器编写NotificationSystem类,代码如下:

public class NotificationSystem
{
		private IMessagingService svc;
		public NotificationSystem(IServiceLocator locator)
		{
			svc = locator.GetMessagingService();
		}

		public void InterestingEventHappened()
		{
			svc.SendMessage();
		}
}

上面的代码假设创建NotificationSystem实例的每个人都会访问服务定位器。这样做带来的便利是,如果应用程序通过服务定位器创建NotificationSystem实例,那么定位器将自身传递到NotificationSystem类的构造函数中;如果是在服务定位器的外部创建NotificationSystem类的实例,还需要提供服务定位器到NotificationSystem类的实现,以便服务定位器找到它的依赖项。

1.2 弱类型服务定位器

如果在某个具体应用中,强类型服务定位器的负面影响超过了它所带来的正面效应,可以考虑改用弱类型服务定位器,代码如下:

public interface IServiceLocator
{
		object GetService(Type serviceType);
}

服务定位器模式的这种变体更加灵活,因为它允许请求任意的服务类型。之所以称之为弱类型服务器,是因为它采用Type类型的参数,并返回一个非类型化的实例,也就是一个Object类型的对象。显然,需要把调用GetService方法返回的结果转换为正确类型的对象。

使用弱类型服务定位器的NotificationSystem类的代码如下:

public class NotificationSystem
{
		private IMessagingService svc;
		public NotificationSystem(IServiceLocator locator)
		{
			svc = (IMessagingService)locator.GetService(typeof(IMessagingService));
		}
		
		public void InterestingEventHappend()
		{
			svc.SendMessage();
		}
}

上面的代码看上去没有先前使用强类型服务定位器的代码简洁,这主要是因为需要把GetServie方法返回的结果转换为IMessagingService接口类型。自从.NET2.0引入泛型以来,我们就已经包含了GetService方法的一个泛型版本:

public interface IServiceLocator
{
		object GetService(Type serviceType);
		TService GetService<TService>();
}

按照泛型方法的约定,它将返回一个已经转换为正确类型的对象,注意返回类型是TService而不是Object。这使得NoticicationSystem类的代码变得简洁些:

public class NotificationSyste
{
		private IMessagingService svc;
		public NotificationSystem(IServiceLocator locator)
		{
			svc = locator.GetService<IMessagingService>();
		}
		public void InterestingEventHappened()
		{
			svc.SendMessage();
		}
}

该方法的负面影响是,它强制IServiceLocator接口必须实现两个几乎相同的方法,而不是只实现一个。这些无谓的努力在.NET3.5中被移除,因为3.5中引入了一个新特性:扩展方法。

把扩展方法作为静态类的表态方法来编写,在它的第一个参数中利用特殊的this关键字来指定扩展方法要附加到的类型。把GetService泛型方法分割成为扩展方法之后,代码如下:

public interface IServiceLocator
{
		object GetService(Type serviceType);
}
public static class ServiceLocatorExtensions
{
		public static TService GetService<TService>(this IServiceLocator locator)
		{
			return (TService)locator.GetService(typeof(TService));
		}
}

2. 依赖注入

依赖注入是另一种控制反转模式的形式,它没有像服务定位器一样的中间对象。相反,组件以一种允许依赖的方式来编写,通常由构造函数参数或属性设置器来显示式表示。

选择依赖注入而不选择服务定位器的开发人员往往都决定选择需求的透明性。

2.1 构造函数注入

依赖注入的最常见形式是构造函数注入。该项技术需要我们为类创一个显示表示所有依赖的构造函数,而不是像先前服务定位器的例子一样,构造函数把服务定位器作为它仅有的参数。

如果采用构造函数注入,NotificationSystem类的代码如下:

public class NotificationSystem
{
		private IMessagingService svc;
		public NotificationSystem(IMessagingService service)
		{
			this.svc = service;
		}
		public void InterestingEventHappened()
		{
			svc.SendMessage();
		}
}

这段代码的一个显著优点是,它极大地简化了构造函数的实现。组件总是期望创建它的类能够传递需要的依赖。而它只需要存储IMessagingService接口的实例以便之后利用。

另外,这段代码减少了NotificationSystem类需要知道的信息量。在以前,NotificationSystem类既要知道服务定位器,也需要知道它自己的依赖项;而现在只需要知道它自己的依赖项就行了。

第三个优点,正如上面提到的,就是需求的透明性。任何想创建NotificationSystem类实例的代码都能查看构造函数,并精确地知道哪些内容是使用NotificationSystem类必须的。

2.2 属性注入

属性注入是一种不太常见的依赖注入方式。顾名思义,该方法是通过设置对象上的公共属性而不是通过使用构造函数参数来注入依赖的。

采用属性注入,NotificationSystem类的代码如下:

public class NotificationSystem
{
		public IMessagingService MessagingService { get; set; }
		public void InterestingEventHappened()
		{
			MessagingService.SendMessage();
		}
}

上面的代码删除了构造函数的参数,事实上,删除了整个构造函数,取而代之的是一个属性。该类其望任何消费都类都通过属性向我们提供依赖。

上面的InterestingEventHappened方法现在有点危险,可能会产生异常。由于它假定服务依赖已经被提供;而在它被调用时,如果没有提供服务依赖,那么它将抛出一个NullReferenceException异常。鉴于以上问题,我们应该更新InterestingEventHappened方法以确保在使用服务之前已提供了服务依赖:

public void InterestingEventHappened()
{
		if(MessagingService == null)
		{
			throw new InvalidOperationException(“Please set MessagingService before calling ” + “InterestingEventHappened().”);
		}
		MessagingService.SendMessage();
}

显而易见,这里我们已经减少了需求的透明性;尽管相对于服务定位器而言,它还算透明,但是它绝对比构造函数注入更容易产生错误。

既然属性注入降低了透明性,那么开发人员为什么仍然选择属性注入而不选择构造函数注放呢?主要有两点:
(1)如果依赖在某种意义上是真正可选的,即在消费者类不提供依赖时,也有相应的处理。些时,属性注入可能是一个不错的选择。
(2)类的实例可能需要在我们还没有控制调用的构造函数的情况下被创建。这是一个不太明显的原因。

—— ASP.NET MVC5 高级编程(第5版)

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
.NET Framework 中使用 MVC 架构时,可以使用依赖注入(Dependency Injection,简称 DI)来解决对象之间的依赖关系。依赖注入是一种设计模式,通过将对象的创建和管理委托给外部容器来实现。 在 MVC 中使用依赖注入的好处是可以降低代码的耦合性,提高可维护性和可测试性。以下是在 .NET Framework MVC 中使用依赖注入的一般步骤: 1. 配置依赖注入容器:您可以使用第三方的依赖注入容器,如 Autofac、Unity、Ninject 等,或者使用 .NET Framework 自带的简单容器(如 `UnityContainer`)。 2. 注册依赖项:在配置阶段,您需要将控制器、服务、存储库等需要被注入的对象注册到容器中。这样容器就能够识别和管理这些对象。 3. 声明依赖关系:在需要使用依赖项的地方(如控制器的构造函数),通过构造函数或属性注入等方式声明依赖关系。容器会自动解析和提供所需的对象实例。 4. 解析依赖项:在运行时,容器会根据对象的声明和注册信息来解析依赖关系,并将所需的对象实例提供给相应的对象。 下面是一个使用 .NET Framework MVC 和 Unity 容器的示例: 首先,安装 Unity 容器的 NuGet 包(例如通过 NuGet 包管理器控制台运行 `Install-Package Unity`)。 然后,在 Global.asax.cs 文件的 Application_Start 方法中配置 Unity 容器: ```csharp protected void Application_Start() { // 创建 Unity 容器 var container = new UnityContainer(); // 注册依赖项 container.RegisterType<IMyService, MyService>(); // 设置 MVC 依赖解析器为 UnityDependencyResolver DependencyResolver.SetResolver(new UnityDependencyResolver(container)); // 其他 MVC 配置... } ``` 接下来,在控制器中声明依赖关系: ```csharp public class MyController : Controller { private readonly IMyService _myService; public MyController(IMyService myService) { _myService = myService; } // 控制器动作方法... } ``` 现在,当 MVC 框架创建 MyController 实例时,Unity 容器会自动解析 IMyService 接口的实现(如 MyService),并将其注入到控制器的构造函数中。 这样,您就可以通过依赖注入来实现对象之间的解耦和灵活性,使代码更加可测试和可扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值