DIP原则、IoC以及DI

一、DIP原则

  • 高层模块不应该依赖于底层模块,二者都应该依赖于抽象。

  • 抽象不应该依赖于细节,细节应该依赖于抽象。

该原则理解起来稍微有点抽象,我们可以将该原则通俗的理解为:"依赖于抽象”

该规则告诉我们,程序中所有的依赖关系都应该终止于抽象类或者接口,从而达到松耦合的目的。因为我们在应用程序中编写的大多数具体类都是不稳定的。我们不想直接依赖于这些不稳定的具体类。通过把它们隐藏在抽象和接口的后面,可以隔离它们的不稳定性。

举个例子

一个Button对象会触发Click方法,当被按下时,会调用Light对象的TurnOn方法,否则会调用Light对象的TurnOff方法。

这个设计存在两个问题:

  1. Button类直接依赖于Light类,这种依赖关系意味着当Light改变时,Button类会受到影响;

  2. Button对象只能控制Light对象,想要控制电视或者冰箱就不行了;

新的设计:

这个方案对那些需要被Button控制的对象提出了一个约束。需要被Button控制的对象必须要实现ISwitchableDevice接口。

所为原则,只是描述了什么是对的,但是并没有说清楚如何去做。在软件工程中,我们经常使用DI(依赖注入)来达到这个目的。但是提到依赖注入,人们又会经常提起IoC这个术语。所以先让我们来了解下什么是IoC。

二、IoC

IoC的全名是Inverse of Control,即控制反转。这一术语并不是用来描述面向对象的某种原则或者模式,IoC体现为一种流程控制的反转,一般用来对框架进行设计。

举个例子

ReportService是一个用来显示报表的流程,该流程包括Trim()Clean()Show()三个环节。

public class ReportService{ 
   private string _data;  

 public ReportService(string data)    {_data = data;}    public void Trim(string data)    {_data = data.Trim();}    public void Clean()    {_data = _data.Replace("@", "");_data = _data.Replace("-", "");        //...other rules}    public void Show()    {Console.WriteLine(_data);}}

客户端通过下面的方式使用该服务:

var reportService = new ReportService(input);
reportService.Trim(input);
reportService.Clean();
reportService.Show();

这样的一个设计体现了过程式的思考方式,客户端依次调用每个环节从而组成了整个报表显示流程,这样的代码体现了:客户端拥有流程控制权

我们来分析下这段代码,ReportService提供了3个可重用的Api,正如ReportService的命名一样,它告诉我们它是一个服务,我们只能重用他提供的三个服务,它无法提供一个打印报表的流程,整个流程是客户端来控制的。

另外,该设计也违反了tell, Don't ask原则。

打印报表作为一个可复用的流程,不但可以提供可复用的流程环节,还可以提供可复用的流程的定义,当我们进行框架设计的时候,往往会将整个流程控制定制在框架之中,然后提供扩展点供客户端定制。这样的思想体现了流程的所有权从客户端到框架的反转。

比如asp.net mvc或者asp.net api框架,内部定义了http消息从请求,model binder,controller的激活,action的执行,返回response
等可复用的流程。同时还提供了每一个环节的可扩展点。

利用以上思想,我们对ReportService重新设计。

新的设计

采用IoC思想重新设计该报表服务,将原来客户端拥有的流程控制权反转在报表服务框架中ReportService这样的命名已经不适合我们的想法,新的实现不但提供了报表打印的相关服务,同时还提供了一个可复用的流程,因此重新命名为ReportEngine。我们可以通过模板方法达到此目的:

public class ReportEngine{    private  string _data;    public ReportEngine(string data)    {_data = data;}    public void Show()    {Trim();Clean();Display();}    public virtual void Trim()    {_data = _data.Trim();}    public virtual void Clean()    {_data = _data.Replace("@", "");_data = _data.Replace("-", "");}    public virtual void Display()    {Console.WriteLine(_data);}}

此时的报表服务在Show()方法中定义好了一组可复用的流程,客户端只需要根据自己的需求重写每个环节即可。客户端可以通过下面的方式使用ReportEngine

var reportEngine=new StringReportEngine(input);
reportEngine.Show();

三、DI(Dependency Injection)

DI即依赖注入,主要解决了2个问题:

  1. 松耦合,由DI容器来创建对象,符合DIP原则;

  2. 符合IoC的思想,整个应用程序事先定义好了一套可工作的流程,通过在客户端替换DI容器中的具体实现达到重写某个组件的目的;

除此之外,使用依赖注入还可以带来以下好处:

  • 促使你写出更加符合面向对象原则的代码,符合优先使用对象组合,而不是继承的原则;

  • 使系统更加具有可测试性;

  • 使系统更加具备可扩展性和可维护性;

  • 由于所有组件都由DI容器管理,所以可以很方便的实现AOP拦截

我记得之前在stackoverflow上看到过类似这样的一个问题:

如何给5岁小孩解释什么叫DI?

得分最高的答案是:小孩在饿的时候只需喊一声我要吃饭即可,而无需关注吃什么饭是怎么来的等问题。

 public class Kid{    private readonly IFoodSupplier _foodSupplier;    public Kid(IFoodSupplier foodSupplier)    {_foodSupplier = foodSupplier;}    public void HaveAMeal()    {        var food = _foodSupplier.GetFood();        //eat}
}

DI的背后是一个DI Container(DI容器)在发挥作用。DI之所以能够工作需要两个步骤:

  1. 将组件注册到DI容器中;

  2. DI容器统一管理所有依赖关系,将依赖组件注入到所需要相应的组件中;

3.1 组件的注册方式

组件注册到DI容器中有3种方式:

  1. 通过XML文件注册

  2. 通过Attribute(Annotation)注册

  3. 通过DI容器提供的API注册

.net平台中的大多数DI框架都通过第三种方式进行组件注册,为了介绍这3种不同的注册方式,我们通过Java平台下的Spring框架简单介绍:Java中的Spring最早以XML文件的方式进行组件注册,发展到目前主要通过Annotation来注册。
假如我们有CustomerRepository接口和相应的实现CustomerRepositoryImpl,下面用三种不同的方式将CustomerRepositoryCustomerRepositoryImpl的对应关系注册在DI容器中:

public interface CustomerRepository {   
 List<Customer> findAll(); }
 
 public class CustomerRepositoryImpl implements CustomerRepository {    public List<Customer> findAll() {    
     List<Customer> customers = new ArrayList<Customer>();Customer customer = new Customer("Bryan","Hansen");customers.add(customer);    
        return customers;} }

3.1.1、xml文件注册

<bean name="customerRepository" class="com.thoughtworks.xml.repository.CustomerRepositoryImpl"/>

3.1.2、Annotation注册

@Repository("customerRepository")

public class CustomerRepositoryImpl implements CustomerRepository {  
 public List<Customer> findAll() {       //...} }

3.1.3、通过Java代码来实现注册

@Configurationpublic class AppConfig {  
 @Bean(name = "customerRepository")  
 public CustomerRepository getCustomerRepository() {    
   return new CustomerRepositoryImpl();} }

3.1.4通过下面的方式从Container来获取一个实例

appContext.getBean("customerService", CustomerService.class);

一旦我们将所有组件都注册在容器中,就可以靠DI容器进行依赖注入了。

3.2 依赖注入的三种方式

3.2.1. 构造器注入

正如上面Kid的实现一样,我们通过构造器来注入IFoodSupplier组件,这种方式也是依赖注入最佳方式。

3.2.2. 属性注入

public class Kid2{  

 public IFoodSupplier FoodSupplier { get; set; }  
 
  public void HaveAMeal()    {      
  
   var food = FoodSupplier.GetFood();        //eat} }

即通过一个可读写的属性完成注入,该方案的缺点在于为了达到依赖注入的目的而破坏了对象的封装性,所以不推荐。

3.2.3 方法注入

通过添加方法的参数来完成注入,一般来说这种方式都可以通过构造器注入的方式来替换,所以也不太常用。值得一提的是asp.net core源码中用到了这种注入方式。

四、依赖注入实例

1、Register Resolve Release Pattern

下面描述了一个很简单的Console application, 所有的组件都通过Castle Windsor容器进行构造器注入:

//registervar  container = new WindsorContainer();
container.Register(Component.For<IParser>().ImplementedBy<Parser>());
container.Register(Component.For<IWriter>().ImplementedBy<Writer>());
container.Register(Component.For<Application>());//resolvevar application = container.Resolve<Application>();
application.Execute("hel--lo, wor--ld");//releasecontainer.Release(application);

这个例子向我们展示了一个最简单的依赖注入使用方式,register所有组件,resolve客户端程序,最后的release步骤向我们展示了如果显示从DI容器得到一个对象,应该显示释放该组件。这一步在大多数情况下并不是必须的,但是在特定场景下会发生内存泄漏。

2、.net平台下依赖注入最佳实践

下面的解决方案描述了一个典型的应用程序分层结构,该分层结构用来描述如何使用Catle windsor进行依赖注入,注意:这并不是一个合理的领域驱动案例,例如我将Domain模型引用到了Application或者ApplicationService程序集中。

处在项目最底层的Repository程序集定义了一组UserRepository及其接口IUserRepository,这样的一个组件如何注册在Windsor Container中呢?Castle提供了一种叫做WindsorInstaller的机制:

public class RepositoryInstaller:IWindsorInstaller{    public void Install(IWindsorContainer container, IConfigurationStore store)    {container.Register(Component.For<IUserRepository>().ImplementedBy<UserRepository>().LifestyleScoped());}
}

该Installer利用Fluent Api定义了IUserRepositoryUserRepository的对应关系,相对于Java中的Spring框架提供的代码注册方式,该方案的优越之处是显而易见的。
另外的重点在于该Installer此时并没有执行,只有当客户端调用此Installer时,该组件才真真注册进容器。这一点很关键,我们后面还会提到。

接下来的ApplicationService层使用了Repository的抽象,一个典型的使用片断如下:

public class UserApplicationService : IUserApplicationService{    

  private
readonly IUserRepository _userRepository;

   public UserApplicationService(IUserRepository userRepository)  
  
{_userRepository = userRepository;}  
   
    public void Register(User user)    {_userRepository.Save(user);}    //.....}

我们通过构造器注入的方式注入了IUserRepository,同时,作为Service层,它也拥有自己的Installer:

public class ApplicationServiceInstaller:IWindsorInstaller{ 

         public void Install(IWindsorContainer container, IConfigurationStore store)    {container.Register(Classes.FromThisAssembly().BasedOn<IApplicationService>().WithServiceDefaultInterfaces().LifestyleScoped());} }

上面的例子示范了如何通过Castle提供的高级api来实现将该程序集中所有继承于IApplicationService的组件和其默认接口一次性全部注册到DI容器中。
比如UserApplicationServiceIUserApplicationService,以及未来将要实现的OrderApplicationService以及IOrderApplicationService

接下来到客户端程序集Application层,Application作为使用ApplicationService程序集的客户端,他才拥有将组件注册进DI容器的能力,我们定义一个ApplicationBootstrap来初始化DI容器并注册组件:

public class ApplicationBootstrap{  

   public static IWindsorContainer Container { get; private set; }  
 
   public static IWindsorContainer  RegisterComponents()    {Container=new WindsorContainer();Container.Install(FromAssembly.This());Container.Install(FromAssembly.Containing<ApplicationServiceInstaller>());Container.Install(FromAssembly.Containing<RepositoryInstaller>());        return Container;} }

注意Container.Install(...)方法将执行不同应用程序的Installer,此时组件才真真注册进DI容器。该实例展示了如何正确的使用依赖注入框架:

  1. 不同的程序集之间通过接口依赖,符合DIP原则;

  2. 通过依赖注入的方式定义好了可运行的流程,但是客户端可以注册不同的组件到DI容器中,符合IoC的思想;

3、如何在asp.net mvc和asp.net webapi使用依赖注入

本文提供的源码中所含的WebApplicationSample项目演示了如何通过自定义WindsorControllerFactory来实现mvc的依赖注入,通过自定义WindsorCompositionRoot实现web api的依赖注入。

五、高级进阶

asp.net core实现了一个还算简单的DI容器DenpendencyInjection,感兴趣的同学可以阅读其源码。

原文地址:http://www.cnblogs.com/richieyang/p/6060160.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/327903.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java IO最详解

转载自 Java IO最详解初学java&#xff0c;一直搞不懂java里面的io关系&#xff0c;在网上找了很多大多都是给个结构图草草描述也看的不是很懂。而且没有结合到java7 的最新技术&#xff0c;所以自己来整理一下&#xff0c;有错的话请指正&#xff0c;也希望大家提出宝贵意见…

hibernate在分层架构中修改数据(update)时遇到的问题!!

开发软件&#xff1a;Myeclipse 10.0 数据库&#xff1a;oracle 开发人员&#xff1a;1111 问题简单描述&#xff1a;修改数据的时候不能正常修改&#xff0c;要么修改不成功&#xff0c;要么报错 nice,下面就来看看怎么解决这个bug的。 首先&#xff0c;我做的是一个租房网站&…

intellij idea 如何一键清除所有断点

intellij idea 如何一键清除所有断点 2017-06-19 11:37:20 yanziit 阅读数 50429更多 分类专栏&#xff1a; idea工具 我之前写了一个百度经验,但是搜不到,现在复制一遍,自己留个记录. 注:此方法适用 intellij idea 2016.2.5版本,其他版本我没用过,暂时不知道 1.在idea左下…

整理下.net分布式系统架构的思路

最近看到有部分招聘信息&#xff0c;要求应聘者说一下分布式系统架构的思路。今天早晨正好有些时间&#xff0c;我也把我们实际在.net方面网站架构的演化路线整理一下&#xff0c;只是我自己的一些想法&#xff0c;欢迎大家批评指正。 首先说明的是.net下开源内容较少&#xff…

SQL注入问题及预防方法

SQL注入问题 sql存在漏洞&#xff0c;会被攻击导致数据泄露 SQL会被拼接 or package com.kuang.lesson02; import com.kuang.lesson02.utils.jdbcUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; …

Java transient关键字使用小记

转载自 Java transient关键字使用小记1. transient的作用及使用方法我们都知道一个对象只要实现了Serilizable接口&#xff0c;这个对象就可以被序列化&#xff0c;java的这种序列化模式为开发者提供了很多便利&#xff0c;我们可以不必关系具体序列化的过程&#xff0c;只要…

mysql sample函数_Oracle SAMPLE 语法应用

Sample抽样函数用于支持数据挖掘。Sample 函数使得数据分析操作在样本数据上进行&#xff0c;而不是在整张表上进行。Sample抽样函数用于支持数据挖掘。Sample 函数使得数据分析操作在样本数据上进行&#xff0c;而不是在整张表上进行。选择10%的记录select * from atest sampl…

docker 买了腾讯服务器后的学习

腾讯云买了 打算用 登录成功后&#xff1a; Last login: Mon Nov 11 13:37:02 2019 from 221.12.17.87 [rootVM_0_13_centos ~]# ip addr #查看ip地址 [rootVM_0_13_centos ~]# uname -r #查看centeros内核版本 docker要求 centeros内核在3.10以上 3.10.0-862.e…

Hibernate中使用Criteria查询及注解——(Dept.java)

Dept.java: 部门表的实体类&#xff1a; package cn.bdqn.hibernate_Criteria.entity;import java.util.HashSet; import java.util.Set;/*** 部门表的实体类* author 1111**/ public class Dept implements java.io.Serializable {// Fieldsprivate Integer deptno;private St…

Connect 2016 白话脱口秀将在B站直播,我们的口号是quot; 微软大法好quot;

今年 Connect 大会的主题是 Big possibilities. Bold technology. 北京时间 11月16日 23&#xff1a;00&#xff0c;Connect();//2016 将开启在线直播&#xff0c;届时红衣主教 Scott Guthrie 和 Scott Hanselman 携众多微软技术大咖将为大家带来超级精彩的主题演讲。丰富的内容…

MySQL(笔记)

数据库总览 有时候查的数据错乱&#xff0c;可以重启MySQL 关系型数据库 ( SQL ) MySQL , Oracle , SQL Server , SQLite , DB2 , …关系型数据库通过外键关联来建立表与表之间的关系 非关系型数据库 ( NOSQL )not only Redis , MongoDB , …非关系型数据库通常指数据以对…

第10章尚硅谷SpringBoot检索

第10章尚硅谷SpringBoot检索 P20、尚硅谷-SpringBoot高级-检索-Elasticsearch简介&安装 P21、尚硅谷-SpringBoot高级-检索-Elasticsearch快速入门 P22、尚硅谷-SpringBoot高级-检索-SpringBoot整合Jest操作ES P23、尚硅谷-SpringBoot高级-检索-整合SpringDataElasticsearch…

python 高维数据_Python数据分析入门|利用NumPy高效处理高维数据

矢量化NumPy数组可以将许多数据处理任务表述为简洁的数组表达式&#xff0c;否则需要编写循环。用数组表达式代替循环的做法&#xff0c;通常被称为矢量化。通常矢量化数组运算要比等价的纯Python方式快上一两个数量级&#xff0c;尤其是各种数值计算。假设我们想要在一组值(网…

Hi Visual Studio for Mac

今晚Connect 2016 , 或者你会兴奋地看到Visual Studio 2017在Docker上调试 &#xff0c; MS SQL on Linux &#xff0c;Azure Functions 还有一堆黑技术....但个人还是十分喜欢一个新产品Visual Studio for Mac 。 八个多月前微软收购了Xamarin, 对于Xamarin 的IDE进行了整合&a…

Python3总结

一、输入输出 &#xff08;1&#xff09;控制台输入 input(promptNone) &#xff08;2&#xff09;控制台输出 print([obj1,...][,sep ][,end\n][,filesys.stdout][,flushFalse])二、数据类型 &#xff08;1&#xff09;整数 class int(x, base10) &#xff08;2&#xff09;浮…

Hibernate中使用Criteria查询及注解——(Emp.java)

Emp.java 员工表的实体类&#xff1a; package cn.bdqn.hibernate_Criteria.entity;import java.util.Date;/*** 员工表的实体类* author Administrator**/ public class Emp implements java.io.Serializable {private Integer empno;private Dept dept;private String ename;…

docker 安装elasticsearch

1.安装jdk $ docker pull openjdk $ docker run -d -it --name myopenjdk openjdk /bin/bash 2.安装elasticsearch docker run -d -p 9200:9200 --name"es" -e ES_JAVA_OPTS"-Xms256m -Xmx256m" elasticsearch:5.6 docker pull elasticsearc…

list 置顶元素_java集合指定元素排序:最前,按照提供的顺序排序?求算法

哈哈哈&#xff0c;不好意思&#xff0c;问了题主那么久...根据我获得需求描述&#xff0c;最后我还是觉得引用新的编程元素来使业务稍微变简单的&#xff0c;我整理到需求应该是&#xff1a;首先给定一个指定关键字的排序&#xff0c;给出一个字符串列表&#xff0c;对列表进行…

Connect 大会的主题 ---微软大法好

今年 Connect 大会的主题是 Big possibilities. Bold technology. 北京时间 11月16日 23&#xff1a;00&#xff0c;Connect();//2016 开始了,红衣主教 Scott Guthrie 和 Scott Hanselman 携众多微软技术大咖将为大家带来超级精彩的主题演讲。Visual Studio 2017在Docker上调试…