技术开发 频道

看Reflection如何增加程序的弹性

  【IT168技术评论】在不更动已编译好程序代码的情况下,却能大幅地影响程序的行为,这便是Reflection的动态威力所在

  许多接触过像Java及C#这类程序语言的程序人,或许对「Reflection(反射)」 这个名词不陌生,但实际上将这个技巧运作在日常开发工作的程序设计者,可能就并不那么多了。

  「Reflection」这个名词,在维基百科的解释是「计算机程序用以观察自身,及修改自身结构和行为的过程」。事实上,透过Reflection技巧,程序在执行时期本身便能够得知自己的外观长相,并且自我修改,甚至自我复制。

  Reflection的作用:得知自己的外观,甚至自我修改与复制

  支持Reflection机制的程序语言众多,大多数都是脚本式(Scripting Language)或是以虚拟机器为基础的程序语言,例如Java、C#、Smalltalk、Python、Ruby、PHP、Perl等。甚至JavaScript也支持Reflection。

  Reflection机制究竟能为程序提供什么样的作用?为什么程序设计者需要动用到Reflection?针对诸如此类问题的答案,还是要回到为什么程序需要在执行时期得知自己的外观长相,甚至进一步自我修改、复制。

  相对于「执行时期」,未使用Reflection机制的程序代码,在编译时期便已为编译器所见。对这样的对象导向程序而言,当某个类别A存在与另一个类别B的互动时,类别B在编译时期的长相,势必已经已为类别A所了解。

  举例来说,对于C++程序而言,类别A欲与类别B互动(例如呼叫它的函数),编译器在编译类别A的程序代码时,必须也要能够得知类别B的宣告及定义。相较于这样的限制,Reflection则让你的程序不必在编译时期便确定此事,而是让程序得以在执行时期,根据一些外在的信息,决定操作的对象以及操作的方式,毋需于编译时期便确定、同时写死这些事情。

  由此可以推想,Reflection是一个十分动态的特性,而且看起来可以为程序注入许多的弹性。

       运用Reflection便能审视自身的特性

  在解释究竟Reflection能够带来什么好处之前,先来看看具体的Reflection机制,以明白透过常见的Reflection支持,在程序中究竟能做到那些事情。我以Java为例介绍,目的不在介绍Java完整的Reflection API,而是透过Java,帮助大家了解Reflection的一般性概念。

  在Java中Relfection机制的源头,就是一个叫「Class」的class(在C#中有一个相似的类别,则叫做Type)。这个类别有点特殊,原因在于此类别的每一个对象都用来表示系统中的每一个类别。

  具体来说,每个Class对象都描述了每个类别的相关信息,也提供你透过它可以进行的一些操作。想要开始Reflection的动作,就必须先取得Class类别的对象。最常被运用到的两个途径,一个便是Object(所有对象皆继承的类别)所提供的getClass()函数,另一个则是Class类别所提供的forName()静态函数。

  前者让你得以取得一个对象(尤其是类型未知的对象)所属的类别,而后者则让你得以指定一个类别的名称后,直接得到该类别对应的Class对象。

  有了Class对象之后,便能「审视」自身的特性,这些特性包括了它隶属于那个Package、类别本身究竟是Public还是Private、继承自那一类别、实作了那些接口等。更重要的是,你可以得知它究竟有那些成员变量以及成员函数(包括建构式)
 

  透过Reflection,不需在程序中明定函数名称、自变量个数和类型

  透过这个自我审视的过程,程序便能够了解它所要处理的对象(尤其是类型未知的对象),究竟具备了什么特质。对运用Reflection的程序而言,所了解到的这些特质,便会影响到该程序的运作行为。

  取得了某类别的成员变量后(在Java中是以Field类别的对象表示),便可以取得该类别对象的成员变量值,也可以设定其值。同样的,取得了某类别的成员函数后(在Java中是以Method类别的对象表示),便可取得该成员函数的回传类型、传入的自变量列表类型,当然更重要的是,Method类别的对象,可被用以呼叫类别对象的相对应成员函数。

  所以假想一个情境,你的程序面临了一个待处理的对象,但你完全不知道它是那个类型,有什么成员变量、有什么成员函数,但你还是可以察觉出这一切,你会知道每个成员变量的名称,每个成员函数的名称、甚至你还可以取得每个成员函数的值、设定它们的值、还可以呼叫每个成员函数,同时传入正确的自变量、正确地取得回传值。

  除此之外,Java还允许程序人透过Class类别的newInstance()函数,产生该类别的对象,或许是透过Constructor类别对象取得建构式并呼叫、藉以执行不同建构式,以不同方式产生类别的对象。

  从以上简短的描述中,你应当能够明白,Reflection让你得以在执行时期处理一些原先在编译时期才能够达成的动作。例如在Java中,你想要产生某个类别的对象,你得在程序中这么写:

  Foo obj = new Foo();

  编译时期就得将类别的名称明确写在程序中,也就是说,编译时期就必须让程序知道这件事。如果你想呼叫某个函数,你得这么写:

  obj->bar(arg);

  函数名称、自变量个数和类型,都必须在程序代码中明确指定。

  但有了Reflection,便不再需要在程序代码中明确指定这些东西。例如,程序可以动态地决定究竟要产生那个类别的对象,你可以从设定档中读取类别的名称、根据使用者的输入值,经过一段逻辑运算之后,决定要产生的类别名称,接着再利用Reflection的机制,产生类别的对象。你也可以动态地得知产生出来的对象拥有那些成员函数,甚至是否具有特定名称的成员函数,接着呼叫这些函数。

  有了Reflection,程序代码在撰写及编译的时间点,毋需明白实际在运行时,究竟会涉及那些类别以及它们各自的行为。你所写下的程序代码,可以完全是对要处理的类别一无所知,也可以是对他们有一点基本的假设(例如要处理的类别都具有相同名称的函数,却没有实作相同的接口,或是继承同样的类别),一切都可以等到执行时期,透过自我审视的能力,了解要面对的对象究竟具备什么特性,再依据相对应的逻辑,动态利用程序代码控制。 当程序毋需将行为写死,便消除了相依性

  有了如此动态的能力,程序代码在撰写时毋需将行为写死,包括要处理的类别、要存取的成员变量、要呼叫的函数等。这大大增加了程序弹性,同时也增加了程序的扩充性。

  举例来说,一个连接数据库的Java系统而言,在编译时期是不需要知道究竟运作时会使用那一个JDBC驱动程序,系统只需要透过某种方式,例如在设定档中指定类别名称,那么程序便可以依据这类别名称,加载相对应的JDBC驱动程序,程序代码中完全可以不涉及具体的JDBC驱动程序究竟为何。

  这不仅消除了一定程度的相依性,相较于那些将数据库连接程序代码以静态的方式附属在程序代码中的做法,一旦遇上了必须变更的时候,上述的作法只需更动JDBC驱动程序在设定档中的名称,毋需改变任何已经编译出来的程序代码。

  在不更动已编译好程序代码的情况下,大幅地影响程序的行为,便是Reflection的动态威力所在。

       关于作者:
      清华大学(台湾)资讯工程系的博士研究生,研究兴趣包括计算机网络、点对点网络、分布式网络管理、以及行动式代理人,专长则是Internet应用系统的开发。曾参与过的开发项目性质十分广泛而且不同,从ERP、PC Game到P2P网络电话都在他的涉猎范围之内。
 

0
相关文章