控制反转(IOC)的简单实现及原理分析

Task

打印输出报表,目前有html和pdf两种格式,以后可能增加。一般打印年度报表和季度报表等。

Solution1

提取一个接口ReportGenerator,里面有一个generator()的抽象方法;HtmlReport和PdfReport是这个接口的两个实现类;ReportService对外提供服务,打印报表。以下是三个类的具体实现。

[java]  view plain  copy
  1. package com.ks.spring.report;  
  2.   
  3. public interface ReportGenerator {    
  4.     public void generator(String[][] table);  
  5. }  
[java]  view plain  copy
  1. package com.ks.spring.report.impl;  
  2.   
  3. import com.ks.spring.report.ReportGenerator;  
  4.   
  5. public class HtmlReport implements ReportGenerator {  
  6.   
  7.     @Override  
  8.     public void generator(String[][] table) {  
  9.         // TODO Auto-generated method stub  
  10.         System.out.println("This is Html report!");  
  11.     }  
  12.   
  13. }  
[java]  view plain  copy
  1. package com.ks.spring.report.impl;  
  2.   
  3. import com.ks.spring.report.ReportGenerator;  
  4.   
  5. public class PdfReport implements ReportGenerator {  
  6.   
  7.     @Override  
  8.     public void generator(String[][] table) {  
  9.         // TODO Auto-generated method stub  
  10.         System.out.println("This is pdf report!");  
  11.     }  
  12.   
  13. }  
[java]  view plain  copy
  1. package com.ks.spring.report.impl;  
  2.   
  3. import com.ks.spring.report.ReportGenerator;  
  4.   
  5. public class ReportService {  
  6.   
  7.     private ReportGenerator reportGen = new PdfReport();  
  8.       
  9.     //打印出某一年的报告  
  10.     public void printAnnualReport(int year){  
  11.         String[][] statistics = null;  
  12.           
  13.         // to do some statistics  
  14.           
  15.         reportGen.generator(statistics);  
  16.     }  
  17.       
  18.     //打印出某年中的某个月的报告  
  19.     public void printMonthReort(int year,int month){  
  20.         String[][] statistics = null;  
  21.           
  22.         //to do some statistics  
  23.           
  24.         reportGen.generator(statistics);  
  25.     }  
  26. }  
Problem:在ReportService类中直接依赖了ReportGenerator的两个实现类。如果需要打印出html格式的报表,就需要重新修改代码!显然不够灵活,且耦合度高

Solution2

为了解决上面的问题,我们引入了Container,也就是容器。因此 HtmlReport、PdfReport、ReportService都是这个容器中的组件(component)。代码如下

[java]  view plain  copy
  1. package com.ks.spring.report.impl;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5.   
  6. import com.ks.spring.report.ReportGenerator;  
  7.   
  8. public class Container {  
  9.       
  10.     //全局变量,是contatiner的引用  
  11.     public static Container instance;  
  12.       
  13.     //通过IDs来管理各个组件  
  14.     private Map<String,Object> components;  
  15.       
  16.     public Container(){  
  17.         components = new HashMap<String,Object>();  
  18.         instance = this;  
  19.           
  20.         ReportGenerator reportGen = new PdfReport();//输出Pdf  
  21.         components.put("reportGenerator",reportGen);  
  22.           
  23.         //这个时候components中已经有reportGenerator组件了  
  24.         ReportService reportSe = new ReportService();  
  25.         components.put("reportService",reportSe);         
  26.     }  
  27.       
  28.     public Object getComponent(String id){  
  29.         return components.get(id);  
  30.     }  
  31.   
  32. }  

ReportService调整为

[java]  view plain  copy
  1. public class ReportService {  
  2.   
  3. //  private ReportGenerator reportGen = new PdfReport();  
  4.     private ReportGenerator reportGen = (ReportGenerator)Container.instance.getComponent("reportGenerator");  
  5.       
  6.     //打印出某一年的报告  
  7.     public void printAnnualReport(int year){  
  8.         String[][] statistics = null;  
  9.           
  10.         // to do some statistics  
  11.           
  12.         reportGen.generator(statistics);  
  13.     }  
  14.       
  15.     //打印出某年中的某个月的报告  
  16.     public void printMonthReort(int year,int month){  
  17.         String[][] statistics = null;  
  18.           
  19.         //to do some statistics  
  20.           
  21.         reportGen.generator(statistics);  
  22.     }  
  23. }  
结论:这样做就把Service类中的依赖给去掉了,外部直接引用Service类,当内部进行改变时,外部的引用对象不需要做相应的修改,换句话说,对外部类屏蔽了底层的实现细节。让Container去依赖generaor的实现。

Problem:当组件需要引用其他数据资源、文件或者其他组件时,需要向容器进行资源请求。这种请求,在系统十分复杂时,很容易造成各组件间的强耦合!

Solution3

传统模式下,当一个组件A需要另一个组件B时,需要在A的代码中new一个B对象。一旦A不需要依赖B而要依赖C时,A就必须更改代码,将new B改为new C。这种模式对于大规模系统来说,简直就是灾难。于是我们引入了控制反转(IOC),我们不需要去请求资源,而是在加载时就将会利用到的资源加载进来。IOC是一种设计原则,从字面意思来说也就是"资源检索的逆转",组件不再需要自己去请求资源了!DI则是实现这一原则的设计模式,它可以通过setter或者构造函数将资源投递(注入)到相应的组件中!显然,Container来实现这一过程~实现代码如下:

[java]  view plain  copy
  1. public class ReportService {  
  2.   
  3. //  private ReportGenerator reportGen = new PdfReport();  
  4. //  private ReportGenerator reportGen = (ReportGenerator)Container.instance.getComponent("reportGenerator");  
  5.     private ReportGenerator reportGen;  
  6.       
  7.     public void setReportGen(ReportGenerator reportGen){  
  8.         this.reportGen = reportGen;  
  9.     }  
  10.       
  11.     //打印出某一年的报告  
  12.     public void printAnnualReport(int year){  
  13.         String[][] statistics = null;  
  14.           
  15.         // to do some statistics  
  16.           
  17.         reportGen.generator(statistics);  
  18.     }  
  19.       
  20.     //打印出某年中的某个月的报告  
  21.     public void printMonthReort(int year,int month){  
  22.         String[][] statistics = null;  
  23.           
  24.         //to do some statistics  
  25.           
  26.         reportGen.generator(statistics);  
  27.     }  
  28. }  
Container类更改如下:

[java]  view plain  copy
  1. public class Container {  
  2.       
  3.     //不需要这个变量了  
  4. //  public static Container instance;  
  5.       
  6.     //通过IDs来管理各个组件  
  7.     private Map<String,Object> components;  
  8.       
  9.     public Container(){  
  10.         components = new HashMap<String,Object>();  
  11. //      instance = this;  
  12.           
  13.         ReportGenerator reportGen = new PdfReport();//输出Pdf  
  14.         components.put("reportGenerator",reportGen);  
  15.           
  16.         //这个时候components中已经有reportGenerator组件了  
  17.         ReportService reportSe = new ReportService();  
  18.         reportSe.setReportGen(reportGen);//在这里进行了注入!!!其实注入也没什么神秘的嘛!  
  19.         components.put("reportService",reportSe);         
  20.     }  
  21.       
  22.     public Object getComponent(String id){  
  23.         return components.get(id);  
  24.     }  
  25.   
  26. }  
结论:这样基本上实现了一个IOC容器,资源通过setter方法把组件的依赖自动注入进去,再也不需要new对象了。

Problem:在container中,是采用pdfReport还是htmlReport?要修改代码就得重新编译!所以spring的IOC容器把这些依赖写到配置文件中,这样就不需要不停地编译啦

Solution4

采用xml或者properties等配置文件来实现组件依赖。本例中为了方便实现,采用properties来完成,需要引入commons-beanutils.jar包,需要调用PropertyUtils.setProperty这个方法。

属性文件如下:

#注入pdfReport到容器中,id是"reportGenerator"
reportGenerator=com.ks.spring.impl.PdfReport

#注入reportService到容器中,id是"reportService"
reportService=com.ks.spring.impl.ReportService

#带"."意思是把"reportGenerator"组件注入到
#reportService组件中的"reportGen"属性中!默认调用setReportGen方法
reportService.reportGen=reportGenerator

Container修改如下:

[java]  view plain  copy
  1. public class Container {  
  2.       
  3.     //通过IDs来管理各个组件  
  4.     private Map<String,Object> components;  
  5.       
  6.     public Container(){  
  7.         components = new HashMap<String,Object>();  
  8.           
  9.         try{  
  10.             Properties proper = new Properties();  
  11.             proper.load(new FileInputStream("components.properties"));  
  12.             for(Entry entry:proper.entrySet()){  
  13.                 String key = (String)entry.getKey();  
  14.                 String value = (String)entry.getValue();  
  15.             }  
  16.         }catch(Exception ex){  
  17.             ex.printStackTrace();  
  18.         }  
  19.     }  
  20.       
  21.     public void processEntry(String key,String value) throws Exception{  
  22.         String[] parts = value.split("\\.");  
  23.         if(parts.length == 1){  
  24.             //新的组件定义  
  25.             Object comp = Class.forName(value).newInstance();  
  26.             components.put(key, comp);  
  27.         }else{  
  28.             //依赖注入  
  29.             Object obj = components.get(parts[0]);  
  30.             Object ref = components.get(value);  
  31.             PropertyUtils.setProperty(obj, parts[1], ref);//根据属性名注入相应的组件  
  32.         }  
  33.     }  
  34.       
  35.     public Object getComponent(String id){  
  36.         return components.get(id);  
  37.     }  
  38.   
  39. }  
就这样,属性文件实现了在container中进行依赖注入!

总结

依赖注入(DI)和控制反转(IOC)从字面上看确实不好理解,咋一看会让初学者望而生畏。类似的讲IOC的文章数不胜数,百度Google一大把,作为初学者却很难找到一篇娓娓道来的文章,根本不会理解得多么深入,仅仅会用而已。我从Spring Recipes这本书中学会了如何一步一步实现简单的IOC容器,从而加深对IOC的理解,所以在此分享下~如有不妥之处,也希望大家指正!

(全文完)


已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页