对象的生命 :: 梦想风暴

来源: BlogBus 原始链接: http://www.blogbus.com:80/blogbus/blog/diary.php?diaryid=456837 存档链接: https://web.archive.org/web/20041204030848id_/http://www.blogbus.com:80/blogbus/blog/diary.php?diaryid=456837


梦想风暴 一个小程序员的信口开河 <<<高手的煽动 | 主页 | 成为开发者>>> 对象的生命 2004-10-23 如同人有生老病死一样,对象同样也有自己生命周期。如果我们将生前和死后也算作对象的状态,那对象一生中将经历两次重大的状态转换。“创建对象”事件将对象混沌的生前状态带入自己的生命旅程,而“销毁对象”时间则宣告对象结束自己生命之旅,成为死后的状态。 在C/C++等传统程序设计语言中,我们――程序员凭借一己之力控制着对象的生杀大权。不错,我提到了C――我们认为与面向对象无关的语言。面向对象是一种思想,与语言并不相关,只不过不支持面向对象特征的语言,比如C,实现起面向对象的特性来说,要走许多弯路罢了。这不是我们讨论的重点,略去不计。 在C中,我们如何初始化一个对象呢?也许说结构更合适一些。分配内存,完成一些相关的初始化动作,简单表示就是这样: my_struct* p = (my_struct*)malloc(size); init_my_struct(p); 鉴于大多数情况下,我们都会完成如此操作,于是到了C++中,出现构造函数(Constructor),这样我们便可以一步到位完成两步操作,当然,原有语法不足以体现这一巨大的进步,C++便引入了相对于C的新语法,代码便成这样: my_class* p = new my_class; 再来看看销毁的过程。本着堆栈先进后出的原则,对比初始化过程,我们应该先完成对象的销毁,再来释放内存: destroy_my_struct(p); free(p); 同样,C++提供了析构函数(destructor)――又一种合二为一的方法: delete p; 按照我们的理解,既然是好东西,那后来者一定会照单全收的。扮演后来者角色的是Java,对于创建的方式,我们发现它确实收下了,但销毁方式呢?我们知道Java中并不存在析构函数的概念,仅有一个类似的finalizer却是众多专家告诫我们远离的对象。地球离开了析构函数还能继续转动吗?Java后来的火爆充分证明了一点,世界少了谁都一样转。 在C++的年代里,我们受到初始教育就是在将需要在内存释放之前完成的销毁动作放入析构函数中,而前面的分析中我们可以看出,真正的销毁实际上是由“销毁对象”、“释放内存”两步完成的,盲点的存在往往会让我们忽略这一现实。 Java的巨大进步之一便是引入了垃圾回收(garbage collection,简称GC)机制。GC所要解决的问题便是困扰了C/C++程序员多年的内存管理问题。GC的出现使我们不必再担心内存没有释放,不必考虑我所指向的内存被别人释放。程序设计语言的发展趋势便是减少程序员对细节的关注。GC的引入将C++中合二为一的析构语义再次分离开来。不同的是,由于Java中并不存在析构函数的概念,如果我们希望完成“销毁动作”,必须自己编写一个销毁函数,然后在代码中显式调用。其实Java完全可以提供一个析构函数的语法,不过语义是“销毁对象”,而没有“释放内存”。不过,这的确会让人――尤其是C++程序员产生足够的误解。 从Java为我们指明的道路中,我们清楚看出了析构函数的非必要性。于是对象生命的终止便成了调用“销毁”函数结束的那一刻。过了这个村,便不再有这个店,对象已经成了孤魂野鬼,对象状态的有效性也无法得到保证。在“销毁”之后调用其它函数,天知道会是什么结果。 既然Java里已经分离了对象结束生命的两个动作,那我们回头考虑一下生命开始的动作吧!显然,我们可以在构造函数中什么都不做,而单独提供一个初始化函数,这样便可以分离创建的两个动作:“分配内存”和“创建对象”。当然,这样简单的分离显然有一种画蛇添足的感觉。 Dependency Injection在Spring和Pico的助力下成了许多人追捧的对象。如果我们使用Setter Injection的方式,代码中会提供一些set方法,他们提供了一种将外部对象“注入”的方式。不过,他们通常不是这个类的主将――业务方法。在实际的编码中,我们通常会在请出主将之前,先调用这些set方法,完成类对象的组装,尽管这些工作可能是一些轻量级容器替我们完成的: MyClass o = new MyClass(); o.setProperty1(property1); ... o.businessMethod(); 在这里,实际上一个对象创建出来之后并不处于一个有效的状态中,我们必须先调用set方法,让它成为一个有效的对象之后,才能使用真正的业务方法。在这种情况下,new只不过是一个简单的对象出生证明,只有完成了set方法的调用,对象能才成长为栋梁之材。 正是因为new出来的对象不能成为一个有效对象,Setter Injection也往往受到来自各方的攻击。其实,既然我们已经接受了“销毁”语义的分离,为什么不能接受“构建”语义的分离呢?不同于我们前面谈到的用一个函数对对象进行初始化的过程,这里我们需要调用完成许多完成许多动作,除了属性设置,我们可能还会有一个特别的初始化方法。在我们完成这些动作之前,对业务方法的调用我们无法保证其正确性。 既然“创建”的过程可以由多步完成,“销毁”的过程也同样,只不过由于销毁对象时对象清楚自己的状态,多步销毁倒显得有些多余了,就像设置完各种属性之后,再调用多个方法初始话一样。 这样看来,C++之父绞尽脑汁加入的构造函数和析构函数岂不成了多余的东西,也不尽然,至少它是一种语法上的便利。如果你想用,有这么个机会。 对象只在完成初始化之后,销毁之前才是有效的对象。这看起来好像是废话,不过,对于我们这些习惯于一切自己动手的程序员而言,摆脱C/C++留给我们的内存回忆还需要一些时间。 初始化和销毁动作的分分合合也正应了《三国演义》的开宗卷语,“天下之事,分久必合,合久必分”。 dreamhead 发表于 2004-10-23 17:10 引用Trackback(0) | 编辑 Comments 发表评论 最近更新 单枪匹马 技术的文字 鱼与熊掌 成为开发者 当Java遭遇OUT参数 新的开始 身体最重要 Hello Velocity之后 Hello Velocity 对象的生命