• 2004-04-15

    二三有别

    Tag:向上走

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://www.blogbus.com/dreamhead-logs/141395.html

    Martin Flower在他的那篇《Inversion of Control Containers and the Dependency Injection pattern》中,提到了三种Injection的形式,它们分别是Interface Injection、Setter Injection和Constructor Injection,对应着IoC中谈到的type 1、type 2、type 3。除了Interface Injection由于较强的侵略性让我有些反感,其它的两种形式,我并没有看出什么不同。Spring和Pico都实现了这两种形式的Injection,只是Spring推崇Setter Injection,而Pico倾向于Constructor Injection。

    一个有趣的小问题向我展示了二者之间一些差别。
    问题是这样的,两个组件,如果A需要B,B也需要A。那么Setter Injection和Constructor Injection是否都能很好的解决问题呢?
    如果你明白了我要说什么,就不必再浪费时间了。如果没有,继续往下看。

    先来看看Setter Injection的代码。
    public class A {
        private B b;

        public void setB(B b) {
            this.b = b;
        }
    }

    public class B {
        private A a;

        public void setA(A a) {
            this.a = a;
        }
    }

    接下来是配置文件。
    <bean id="a" class="A">
        <property name="b">
            <ref bean="b"/>
        </property>
    </bean>

    <bean id="b" class="B">
        <property name="a">
            <ref bean="a"/>
        </property>
    </bean>

    如果用Constructor Injection来完成,代码就变成了这样。
    public class A {
        private B b;

        public A(B b) {
            this.b = b;
        }
    }

    public class B {
        private A a;

        public B(A a) {
            this.a = a;
        }
    }

    配置文件也随之发生了变化。
    <bean id="a" class="A">
        <constructor-arg>
            <ref bean="b"/>
        </constructor-arg>
    </bean>

    <bean id="b" class="B">
        <constructor-arg>
            <ref bean="a"/>
        </constructor-arg>
    </bean>

    显然,我们无法从字面上看出问题来,事实上,即便编译器也无法发现问题所在。那就让我们把代码运行起来。我们的测试代码也不用很复杂,读文件之后,取个bean就可以了。
    InputStream is = new FileInputStream("config.xml");
    BeanFactory factory = new XmlBeanFactory(is);
    factory.getBean("a");

    看到什么了?
    Setter一切正常,而Constructor疯狂的滚屏,直至堆栈溢出。

    解释这个现象很容易。想想如果我们是Spring的作者,我们如何来完成这两个组件间的组合。
    如果是Setter,遇到A,我们用缺省的构造函数将它构造出来,发现它需要B作为它的一个属性,因为B不存在,我们构造B。B的构造过程用的也是缺省的构造函数。发现B需要A作为它的一个属性,因为A已经存在了,就把A加进来。B完成了,回头用它把它加入到A中。
    而Constructor则有所不同。遇到A,我们构造A,但A的构造参数需要B,因为B不存在,我们构造B,结果B又需要A,我们只好再构造A,而A需要B,如此反复,直至堆栈溢出。

    由此可见,如果需要两个组件互相知晓,通过上面的这种Constructor Injection方式显然行不通。当然,我们可以找到其它的变通手法。

    或许你会问,为什么这两个组件要互相知晓?这是一个很正常的需求,比如在MVC框架中,用户在View上的操作显然要View知道Controller在哪里才能传递过去,而Controller也需要知道View在哪才能将一些处理结果让View反映给用户。

    总结一下。当两个组件需要相互知晓时,Setter Injection表现得明显要比Constructor Injection好,属于心理素质好,能够正常发挥的那种,这种精神值得中国足球队的小伙子们好好学习一下。

    分享到:

    历史上的今天:

    引用地址:

    评论

  • 我记得pico的wiki是有介绍过它是怎么处理循环构造的。
  • 这只是spring的问题吧,没记错的话pico是可以处理循环引用的
  • 我只是说习惯于用Type 2
    回复BlueDavy说:
    这叫“萝卜白菜,各有所爱”。^_^
    2004-04-20 22:07:54