反射如何不会导致代码味道?

Lir*_*una 63 reflection

我来自低级语言--C++是我编程的最高级别.

最近我遇到了Reflection,我无法理解如何在没有代码味道的情况下使用它.

在我看来,在运行期间检查类/方法/函数的想法指出了设计中的一个缺陷 - 我认为反射(试图)解决的大多数问题可以与多态性或正确使用继承一起使用.

我错了吗?我是否误解了反思的概念和效用?

我正在寻找一个很好的解释,说明何时使用Reflection,其他解决方案将失败或实现过于繁琐以及何时不使用它.

请指教这个低级别的笨蛋.

Jul*_*iet 90

反射最常用于规避静态类型系统,但它也有一些有趣的用例:

我们来写一个ORM吧!

如果您熟悉NHibernate或大多数其他ORM,您可以编写映射到数据库中的表的类,如下所示:

// used to hook into the ORMs innards
public class ActiveRecordBase
{
    public void Save();
}

public class User : ActiveRecordBase
{
    public int ID { get; set; }
    public string UserName { get; set; }
    // ...   
}
Run Code Online (Sandbox Code Playgroud)

你觉得这个Save()方法是怎么写的?好吧,在大多数ORM中,Save方法不知道派生类中的字段,但它可以使用反射访问它们.

完全可以以类型安全的方式具有相同的功能,只需要求用户覆盖将字段复制到数据行对象的方法,但这会导致大量的样板代码和膨胀.

存根!

Rhino Mocks是一个嘲弄的框架.您将接口类型传递给方法,并且在后台,框架将动态构造并实例化实现接口的模拟对象.

当然,程序员可以手工编写模拟对象的样板代码,但为什么如果框架会为她做呢?

元数据!

我们可以使用属性(元数据)来装饰方法,这些方法可以用于各种目的:

[FilePermission(Context.AllAccess)]    // writes things to a file
[Logging(LogMethod.None)]              // logger doesn't log this method
[MethodAccessSecurity(Role="Admin")]   // user must be in "Admin" group to invoke method
[Validation(ValidationType.NotNull, "reportName")] // throws exception if reportName is null
public void RunDailyReports(string reportName) { ... }
Run Code Online (Sandbox Code Playgroud)

您需要反思该方法以检查属性..NET的大多数AOP框架都使用属性进行策略注入.

当然,你可以内联编写相同类型的代码,但这种风格更具说明性.

让我们创建一个依赖框架!

许多IoC容器需要一定程度的反射才能正常运行.例如:

public class FileValidator
{
    public FileValidator(ILogger logger) { ... }
}

// client code
var validator = IoC.Resolve<FileValidator>();
Run Code Online (Sandbox Code Playgroud)

我们的Io​​C容器将实例化一个文件验证器,并将适当的ILogger实现传递给构造函数.哪个实施?这取决于它的实施方式.

假设我在配置文件中给出了程序集和类的名称.该语言需要将类的名称作为字符串读取,并使用反射来实例化它.

除非我们在编译时知道实现,否则没有类型安全的方法来基于其名称实例化类.

晚结合/鸭子打字

您有多种原因需要在运行时读取对象的属性.我选择日志记录作为最简单的用例 - 假设您正在编写一个记录器,它接受任何对象并将其所有属性都吐出到文件中.

public static void Log(string msg, object state) { ... }
Run Code Online (Sandbox Code Playgroud)

可以覆盖所有可能的静态类型的Log方法,或者您可以只使用反射来读取属性.

某些语言(如OCaml和Scala)支持静态检查鸭子类型(称为结构类型),但有时您只是没有对象接口的编译时知识.

或者正如Java程序员所知,有时类型系统会让你的方式,并要求你编写各种样板代码.有一篇着名的文章描述了动态类型简化了多少设计模式.

偶尔绕过类型系统允许您比静态类型更进一步重构代码,从而产生更清晰的代码(最好隐藏在程序员友好的API背后:)).许多现代静态语言正在采用黄金法则"尽可能静态打字,必要时动态打字",允许用户在静态和动态代码之间切换.

  • +1表示元数据/属性.无处不在,他们在我的世界中,我甚至不再把它们视为反射(尽管它们显然是). (5认同)

Aar*_*ght 11

的项目,如休眠(O/R映射)和StructureMap(依赖注入)而没有反射是不可能的.如何单独使用多态解决这些问题?

是什么让这些问题难以解决的任何其他方式是图书馆不直接知道你的类层次结构 - 他们不能.然而,他们需要知道类的结构,以便 - 例如 - 仅使用字段名称和属性名称将数据库中的任意数据行映射到类中的属性.

反射对于映射问题特别有用.约定代码的概念正变得越来越流行,你需要某种类型的反射才能做到这一点.

在.NET 3.5+中,您有另一种选择,即使用表达式树.这些是强类型的,并且使用反射经典解决的许多问题已经使用lambdas和表达式树重新实现(参见Fluent NHibernate,Ninject).但请记住,并非每种语言都支持这些结构; 当它们不可用时,你基本上坚持反思.

在某种程度上(我希望我没有用这个来惹恼太多),Reflection经常被用作面向对象语言的解决方法/黑客,用于功能语言中免费提供的功能.随着函数式语言变得越来越流行,和/或更多的OO语言开始实现更多功能特性(如C#),我们很可能会开始看到Reflection的使用越来越少.但我怀疑它总是会存在,对于像插件这样的传统应用程序(正如其他响应者之一所指出的那样).


Dam*_*let 8

实际上,您每天都在使用反射系统:您的计算机.

当然,它不是类,方法和对象,而是程序和文件.程序创建和修改文件就像方法创建和修改对象一样.但是程序本身就是文件,有些程序甚至会检查或创建其他程序!

那么,为什么Linux安装能够反思,没有人会想到它,并且对OO程序感到恐惧,为什么呢?


小智 6

我已经看到了自定义属性的好用法.比如数据库框架.

[DatabaseColumn("UserID")]
[PrimaryKey]
public Int32 UserID { get; set; }
Run Code Online (Sandbox Code Playgroud)

然后可以使用反射来获得有关这些字段的更多信息.我很确定LINQ To SQL做了类似的事情......

其他例子包括测试框架......

[Test]
public void TestSomething()
{
    Assert.AreEqual(5, 10);
}
Run Code Online (Sandbox Code Playgroud)


Ale*_*min 6

如果没有反思,你经常需要重复自己.

考虑以下情况:

  • 运行一组方法,例如测试用例中的testXXX()方法
  • 在gui构建器中生成属性列表
  • 使您的类可编写脚本
  • 实现序列化方案

如果不在代码中的其他地方重复受影响的方法和属性的完整列表,通常不能在C/C++中执行这些操作.

事实上,C/C++程序员经常使用接口描述语言在运行时公开接口(提供一种反射形式).

明智地使用反射和注释以及明确定义的编码约定可以避免繁琐的代码重复并提高可维护性.