面向对象的设计原则(2)——依赖注入

面向对象的设计原则(1)中提到的设计方法都是一种数据组织的一种方式,这几种都是 主动依赖(主动去索取对象)  业务的东西都需要依赖框架的东西
 
1、如果依赖的框架坏了
2、或者要由静态工厂 改为 环境变量   这两种情况都需要改变
 
为了解决上述问题,引入  对象注入依赖注入——电视依赖面板、依赖反转——框架的东西依赖于业务的东西)
 
例1:
  1 package objectoriented.coupling5;
  2 
  3 import java.util.HashMap;
  4 import java.util.Map;
  5 
  6 
  7 public class Example {
  8     public static void main(String[] args) {
  9         Context context = new Context();
 10         Television tv = (Television) context.getService("tv"); //取出来的电视剧这个对象,它所需要的部件已经被装配好并且已经被实例化好了
 11         
 12         tv.play();
 13     }
 14 }
 15 
 16 /*1、上下文的类(框架类),框架类依赖于业务类
 17  * 2、该框架类包含一全局注册表(全局map)
 18  * 3、框架类的构造方法完成对象的注入(比如电视需要用面板拼装):
 19  *     前提:电视类需要有一个设置面板类的方法,void setPanel(Panel panel)
 20  *     (1)new一个面板实例,注册到map
 21  *  (2)new一个电视实例,调用setPanel方法完成面板拼装——这一个过程就称为对象注入,然后也注册到map
 22  * 4、调用的地方(比如main方法)从全局map取出电视对象,就可以操作电视了
 23  * 
 24  */
 25 
 26 
 27 /*上下文*/
 28 class Context {
 29     private final Map<String, Object> services = new HashMap<String, Object>();  //类似于全局注册表
 30     
 31     /*表达了不同组价是如何构造起来的一种关系*/
 32     // 框架类依赖于业务类,但业务类不依赖于框架类:依赖反转
 33     // Spring就用xml文件来表示,然后利用反射
 34     public Context() {
 35         // 此部分为各个组价的装配方式,可以使用配置文件实现,然后再用反射加载类
 36         LcdPanel panel = new LcdPanel();
 37         registerService("panel", panel);
 38         
 39         /*new, 然后马上调用set方法完成对象注入;如果new之后把这个对象放入全局中,
 40          * 还未对象注入时就被另一个线程调用(或者忘了调用对象注入了)会报空指针;*/
 41         Television tv = new Television();
 42         tv.setPanel(panel);    //主动的装配过程,完成了电视和面板的组装——对象的注入
 43         registerService("tv", tv);
 44     }
 45     
 46     public Object getService(String serviceName) {
 47         return services.get(serviceName);
 48     }
 49     
 50     private void registerService(String serviceName, Object service) {
 51         services.put(serviceName, service);
 52     }
 53 }
 54 
 55 
 56 
 57 /*没有从框架中进行任何索取我们需要的资源,而是有一个set方法,这个方法
 58  * 任何人包括框架都能调用
 59  * 
 60  * 电视剧没有任何框架代码存在*/
 61 class Television {
 62     private Panel panel;  //没有new及其它任何的初始化
 63                           // 使用组合比使用继承好,电视机使用组合的方式拼装了一个面板,面板可以在运行时很灵活的改变
 64     
 65     void setPanel(Panel panel) { //每一个属性,都需要一个set方法,让它能够被注入进来,使得面板能够被真正装配起来 
 66                                 //运行时传入一个lcd外的其他面板,下次播放时,可以无缝切换
 67         this.panel = panel;
 68     }
 69     
 70     void play() {
 71         panel.display();
 72     }
 73 }
 74 
 75 
 76 interface Panel {
 77     abstract public void display();
 78 }
 79 
 80 class LcdPanel implements Panel {
 81     int size = 32;
 82     
 83     LcdPanel() {
 84     }
 85     
 86     LcdPanel(int size) {
 87         this.size = size;
 88     }
 89     
 90     public void display() {
 91         System.out.println("尺寸=" + size + ",LcdPanel现在开始播放...");
 92     }
 93 }
 94 
 95 class CrtPanel implements Panel {
 96     int size = 32;
 97     
 98     CrtPanel() {
 99     }
100     
101     CrtPanel(int size) {
102         this.size = size;
103     }
104     
105     public void display() {
106         System.out.println("尺寸=" + size + ",CrtPanel现在开始播放...");
107     }
108 }

原理与实现:

/*1、上下文的类(框架类),框架类依赖于业务类
 * 2、该框架类包含一全局注册表(全局map)
 * 3、框架类的构造方法完成对象的注入(比如电视需要用面板拼装):
 *     前提:电视类需要有一个设置面板类的方法,void setPanel(Panel panel)
 *     (1)new一个面板实例,注册到map
 *  (2)new一个电视实例,调用setPanel方法完成面板拼装——这一个过程就称为对象注入,然后也注册到map
 * 4、调用的地方(比如main方法)从全局map取出电视对象,就可以操作电视了
 */

对象注入

  1、依赖注入(Dependency Injection)——对象通过注入的方式组织起来

  2、控制反转(Inversion of Contorl)——框架对业务代码无侵入,即业务代码不依赖于框架代码,反过来框架代码依赖于业务代码

 

Spring的对象注入方式:

1、Setter Injection(类似于上面的例子)

  (1)每个依赖都有setter方法,简单直观

       (2)可以在运行时手动改变依赖——// 此部分为各个组价的装配方式,可以使用配置文件实现,然后再用反射加载类
                                                               LcdPanel panel = new LcdPanel();

  (3)不足:对象的创建和依赖注入是两个步骤

/*new, 然后马上调用set方法完成对象注入;如果new之后把这个对象放入全局中,
         * 还未对象注入时就被另一个线程调用(或者忘了调用对象注入了)会报空指针;*/

2、Construction Injection

  (1)对象的创建和注入是原子操作——new的时候  必须一起提供需要的所有属性

  (2)不足:构造器参数过多

  (3)不足:依赖不能修改——当应用本身就要求不能修改时,用构造器注入比较安全

3、Interface Injection——用的不多

例2:google-guice : 基于Java5的轻型依赖注入框架

 1 package guiceexample;
 2 
 3 import com.google.inject.AbstractModule;
 4 import com.google.inject.Guice;
 5 import com.google.inject.Inject;
 6 import com.google.inject.Injector;
 7 
 8 //如下两个不引入编译不报错,运行时Exception in thread "main" java.lang.NoClassDefFoundError: javax/inject/Provider等
 9 import javax.inject.Provider;
10 import org.aopalliance.intercept.MethodInterceptor;
11 
12 
13 public class Example {
14     public static void main(String[] args) {
15         Injector injector = Guice.createInjector(new MyModule());
16         Television tv = injector.getInstance(Television.class); //类似于全局注册表
17         tv.play();
18     }
19 }
20 
21 
22 //这个Module掌管了三个东西(configure方法里面),具体面板如何装配电视是不需要在这里体现的,只要在Television里面的Panel对象前加入@Inject
23 class MyModule extends AbstractModule {
24     @Override  
25     protected void configure() {  //说明这些对象之间是如何进行装配的
26         bind(Panel.class).to(LcdPanel.class);  //接口和实现类关联起来
27     //    bind(Panel.class).to(CrtPanel.class);
28         bind(Television.class);
29     }
30     
31 }
32 
33 
34 class Television {
35     @Inject  
36     private Panel panel; //使用的时候只要加上@Inject注解,guice自然知道电视是需要注入一个面板的,它就会自动帮你完成注入
37                          //业务代码中,这里需要装配的代码多半都是抽象的
38     void play() {
39         panel.display();
40     }
41 }
42 
43 
44 
45 interface Panel {
46     abstract public void display();
47 }
48 
49 class LcdPanel implements Panel {
50     int size = 32;
51     
52     LcdPanel() {
53     }
54     
55     LcdPanel(int size) {
56         this.size = size;
57     }
58     
59     public void display() {
60         System.out.println("尺寸=" + size + ",LcdPanel现在开始播放...");
61     }
62 }
63 
64 class CrtPanel implements Panel {
65     int size = 32;
66     
67     CrtPanel() {
68     }
69     
70     CrtPanel(int size) {
71         this.size = size;
72     }
73     
74     public void display() {
75         System.out.println("尺寸=" + size + ",CrtPanel现在开始播放...");
76     }
77 }

注意1:下载guice-3.0,工程里需要引入guice-3.0.jar、javax.inject.jar、aopalliance.jar

注意2:如果放开第27行,会报如下的错误——A binding to guiceexample.Panel was already configured at guiceexample.MyModule.configure

 

Guice与Spring的不同:

1、专注于依赖注入

2、性能号称是Spring的10倍

3、无配置文件、类型安全,支持编译器重构

4、简单

Spring:做了很多跟框架的粘合,跟Structs、跟持久化框架(hibernate)粘合
Spring3也在做很多无配置文件的注入
 
总结:
1、依赖抽象,而不是依赖实现
2、业务不依赖于框架,框架服务业务
 
Televsion与Panle等装配方之间降低耦合
也使得业务代码与框架代码的依赖方式发生转变,我们通常说的是依赖抽象,而不是依赖于具体的实现
class Television {
@Inject  
private Panel panel; 
                     //业务代码中,这里需要装配的代码多半都是抽象的;具体的面板发生升级的时候,电视机这个类不需要变更

posted on 2013-11-23 19:50  gogoy  阅读(591)  评论(0编辑  收藏  举报

导航