• 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
  • 呵呵~~~~,多谢你们钻研的这末深入,我就从没想过此类问题,下次碰见堆栈溢出,可以轻松的找到原因了...
  • 我纳闷的是找出这样例子的理由?

    这样稀有的例子还真有意思,要是在实际工作中,

    我会始终简单的坚持构造器型,当遇到这样变态的情况,可以

    局部的使用一下setter型:

    public class A {

    private B b;



    public void setB(B b) {

    this.b = b;

    }

    }

    public class B {

    private A a;



    public B(A a) {

    this.a = a;

    }

    }

    <bean id="a" class="A">

    <property name="b">

    <ref bean="b"/>

    </property>

    </bean>

    <bean id="b" class="B">

    <constructor-arg>

    <ref bean="a"/>

    </constructor-arg>

    </bean>
    回复iseeisee说:
    这个例子不是我空想出来的,而是我根据一个同事提出的问题简化而来的。我们习惯于使用type 2,他的问题也是基于type 2提出的。正是有了这么一个机会,使我对type 2和type 3做了稍微思考。我并不鼓吹type 2或是type 3,大家完全可以根据自己的喜好来。我只是把我发现的问题分享给大家。
    2004-04-17 22:46:50
  • 我意见相反,构造器

    1.更少代码

    2.型运行期更安全

    3.更明白我的类在用谁在干啥
  • type2 的setter方法就像javaben,看起来很美de
  • Type2和Type3各有千秋吧,反正Spring和Pico都是同时支持的,只是有偏向而已,依实际情况而定使用了,我更喜欢Type2
    回复BlueDavy说:
    我可没说type 2强于type 3,虽然我的例子不利于type 3,虽然我个人也喜欢type 2。^_^
    2004-04-16 10:44:11