我不知道为什么,但是当我使用反射时,我总是觉得自己在“作弊”-也许是因为我知道我要表现出色。
我的一部分说,如果它是您正在使用的语言的一部分,并且可以完成您尝试做的事情,那么为什么不使用它呢?我的另一部分说,必须有一种无需使用反射即可执行此操作的方法。我想也许这取决于情况。
使用反射时需要注意哪些潜在问题,我应该如何关注它们?尝试寻找更常规的解决方案需要花费多少精力?
我不知道为什么,但是当我使用反射时,我总是觉得自己在“作弊”-也许是因为我知道我要表现出色。
我的一部分说,如果它是您正在使用的语言的一部分,并且可以完成您尝试做的事情,那么为什么不使用它呢?我的另一部分说,必须有一种无需使用反射即可执行此操作的方法。我想也许这取决于情况。
使用反射时需要注意哪些潜在问题,我应该如何关注它们?尝试寻找更常规的解决方案需要花费多少精力?
Answers:
不,这不是在作弊-这是一种解决某些编程语言问题的方法。
现在,它通常不是最佳的(最干净,最简单,最容易维护的)解决方案。如果有更好的方法,请确实使用该方法。但是,有时没有。或者,如果存在的话,它会变得更加复杂,涉及大量的代码重复等,从而使其不可行(从长远来看很难维护)。
当前项目(Java)中的两个示例:
fieldX
为该元素的XML元素与该类中的适当字段进行匹配,并对其进行初始化。在某些情况下,它可以根据所标识的属性即时构建一个简单的GUI对话框。如果没有反思,这将需要跨多个应用程序使用数百行代码。因此,反思帮助我们快速构建了一个简单的工具,而不必大惊小怪,并使我们能够专注于重要的部分(对Web应用程序进行回归测试,分析服务器日志等),而不是无关紧要的部分。像其他任何强大的工具一样,最重要的是反射也可以用来射击自己的脚。如果您了解何时以及如何使用(不使用)它,它将为您带来优雅而干净的解决方案,以解决其他困难的问题。如果滥用它,则可以将原本很简单的问题变成一个复杂而丑陋的混乱局面。
if (propName = n) setN(propValue);
,您可以将您的XML标记命名为与代码属性相同的名称,并对其进行循环。此方法还使以后添加属性变得更加简单。
这不是在作弊。但这至少在生产代码中是个坏主意,至少由于以下原因:
我建议将反射的使用限制在以下情况下:
在所有其他情况下,我建议找出一种避免反射的方法。使用适当的方法定义接口并在要调用该方法的类集上实现它通常足以解决大多数简单情况。
反射只是元编程的另一种形式,并且与当今大多数语言中基于类型的参数一样有效。反射功能强大且通用,反射程序是高度可维护性的(当然,如果正确使用的话)比纯面向对象程序或过程程序更重要。是的,您为性能付出了代价,但是我很乐意选择一个速度较慢的程序,该程序在很多甚至大多数情况下都可以维护。
当然,这完全取决于您要实现的目标。
例如,我编写了一个媒体检查器应用程序,该应用程序使用依赖项注入来确定要检查的媒体类型(MP3文件或JPEG文件)。该外壳需要显示一个网格,其中包含每种类型的相关信息,但是它不知道要显示什么。这是在读取该类型媒体的程序集中定义的。
因此,我必须使用反射来获取要显示的列数及其类型和名称,以便可以正确设置网格。这也意味着我可以在不更改任何其他代码或配置文件的情况下更新注入的库(或创建一个新库)。
唯一的其他方法是拥有一个配置文件,当我切换要检查的媒体类型时,该文件需要更新。这将为应用程序带来另一个故障点。
如果您是图书馆作者,那么反射是一个了不起的工具,因此对传入的数据没有影响。反射和元编程的结合可以使您的库与任意调用者无缝协作,而无需他们跳过代码生成等方面。
但是,我确实不鼓励在应用程序代码中进行反思。在应用程序层,您应该使用不同的隐喻-接口,抽象,封装等。
反射对于为开发人员构建工具而言是极好的。
因为它允许您的构建环境检查代码,并可能生成正确的工具来操纵/初始化检查代码。
作为一种通用的编程技术,它可能有用,但比大多数人想象的要脆弱。
反射(IMO)真正用于开发的用途是,它使编写通用流库非常简单(只要您的类描述永不改变(然后它就变得非常脆弱))。
如果不加意识地使用反射,在OO语言中通常是有害的。
我已经记不清我在StackExchange网站上看到过多少个坏问题了,
OO的重点是
如果在代码中的任何一点,第2点对您已传递的对象无效,则其中一个或多个为true
技能欠佳的开发人员根本不会理解这一点,他们认为可以在代码的任何部分传递任何内容,并从一组(硬编码的)可能性中进行所需的操作。这些白痴使用反射很多。
对于OO语言,仅应在元活动(类加载器,依赖项注入等)中进行反射。在这些情况下,需要进行反思,因为您正在提供通用服务来协助对出于正当理由而一无所知的代码进行操作/配置。在几乎任何其他情况下,如果您需要反思,那么您就在做错事,您需要问自己为什么这段代码对传递给它的对象不足够了解。
在反射类的域定义明确的情况下,一种替代方法是使用反射以及其他元数据来生成代码,而不是在运行时使用反射。我使用FreeMarker / FMPP来完成;还有很多其他工具可供选择。这样做的好处是最终会产生易于调试的“真实”代码,等等。
根据情况,这可以帮助您更快地编写代码-或仅使大量代码膨胀。它避免了反射的缺点:
之前提到。
如果反射感觉像是在作弊,那可能是因为您基于不确定性很大的猜测,而您的直觉警告您这是有风险的。确保提供一种方法来增强您自己的元数据反射中固有的元数据,您可以在其中描述您可能会遇到的现实类的所有怪癖和特殊情况。
它不是在作弊,但像其他任何工具一样,应将其用于要解决的问题。根据定义,反射使您可以通过代码检查和修改代码;如果那是您需要做的,那么反思就是完成这项工作的工具。反思全都与元代码有关:以代码为目标的代码(与以数据为目标的常规代码相反)。
良好的反射用法的一个示例是通用的Web服务接口类:一种典型的设计是将协议实现与有效负载功能分开。因此,您有一个T
实现您的有效负载的类(我们称之为),而另一个实现了协议(P
)的类。T
非常简单:对于您要进行的每个调用,只需编写一个方法即可完成应有的功能。P
但是,需要将Web服务调用映射到方法调用。使这种映射通用是可取的,因为它避免了冗余,并P
具有很高的可重用性。反射提供了一种T
在运行时检查类并基于P
通过Web服务协议传递的字符串来调用其方法的方法,而无需任何编译时对类的了解T
。使用“关于代码的代码”规则,可以认为类将类P
中的代码T
作为其数据的一部分。
然而。
反射还为您提供了解决语言类型系统限制的工具-理论上,您可以将所有参数作为type传递object
,并通过反射调用其方法。Voilà,一种本来应该强制执行严格的静态键入规则的语言,现在的行为就像具有后期绑定的动态类型的语言一样,只是其语法要复杂得多。到目前为止,我所见过的这种模式的每个实例都是一个肮脏的hack,而且在语言的类型系统中总是有可能提供一种解决方案,并且在所有方面都将是更安全,更优雅,更高效的。
存在一些例外情况,例如可以将控件绑定到各种不相关类型的数据源的GUI控件。强制您的数据实现某个接口只是为了对它进行数据绑定是不现实的,而且程序员也没有为每种类型的数据源实现适配器。在这种情况下,使用反射来检测数据源的类型并调整数据绑定是一个更有用的选择。
这完全取决于。没有反射就很难做到的一个例子是复制ObjectListView。它还会即时生成IL代码。
反思可以实现实际上无法通过其他方式完成的工作。
例如,考虑如何优化此代码:
int PoorHash(char Operator, int seed, IEnumerable<int> values) {
foreach (var v in values) {
seed += 1;
switch (char) {
case '+': seed += v; break;
case '^': seed ^= v; break;
case '-': seed -= v; break;
...
}
seed *= 3;
}
return seed;
}
内部循环的中间有一个昂贵的测试提示,但是要提取它,需要为每个操作员重写一次循环。反射使我们能够获得与提取测试相当的性能,而无需重复执行多次循环(从而牺牲了可维护性)。只需动态生成并编译所需的循环即可。
实际上,我已经完成了此优化,尽管情况有些复杂,结果却是惊人的。性能提高了一个数量级,更少的代码行。
(注意:我最初尝试传递一个Func而不是一个char等效,它稍好一些,但不能达到近10倍的反射率。)
它绝不是骗人的……相反,它使应用程序服务器能够使用用户选择的名称来运行用户创建的类,从而为用户提供了灵活性,而不是骗人。
而且,如果您想查看任何.class文件(在Java中)的代码,那么可以免费使用几个反编译器!