面向对象的设计原则(2)——依赖注入
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、简单