何时使用嵌套类和嵌套在模块中的类?

Posted

技术标签:

【中文标题】何时使用嵌套类和嵌套在模块中的类?【英文标题】:When to use nested classes and classes nested in modules? 【发布时间】:2011-09-05 22:37:59 【问题描述】:

我非常熟悉何时使用子类和模块,但最近我看到了这样的嵌套类:

class Foo
  class Bar
    # do some useful things
  end
end

以及像这样嵌套在模块中的类:

module Baz
  class Quux
    # more code
  end
end

文档和文章很少,或者我没有受过足够的教育,无法找到正确的搜索词,但我似乎无法找到有关该主题的太多信息。

有人可以提供示例或帖子链接,说明为什么/何时使用这些技术?

【问题讨论】:

【参考方案1】:

在 Ruby 中,定义嵌套类类似于在模块中定义类。它实际上并不强制类之间的关联,它只是为常量创建一个命名空间。 (类和模块名称是常量。)

接受的答案对任何事情都不正确。在下面的示例中,我创建了一个词法封闭类的实例,而没有封闭类的实例。

class A; class B; end; end
A::B.new

优点与模块的相同:封装,将只在一个地方使用的代码分组,并将代码放置在更靠近使用它的地方。一个大型项目可能有一个外部模块,它在每个源文件中反复出现,并包含许多类定义。当各种框架和库代码都这样做时,它们各自只为顶层贡献一个名称,从而减少了冲突的机会。可以肯定的是,平淡无奇,但这就是使用它们的原因。

使用类而不是模块来定义外部命名空间可能在单文件程序或脚本中有意义,或者如果您已经将***类用于某事,或者如果您实际上要添加代码来链接这些类以真正的 inner-class 样式组合在一起。 Ruby 没有内部类,但没有什么能阻止您在代码中创建大致相同的行为。从内部对象引用外部对象仍然需要从外部对象的实例中点入,但是嵌套类将表明这就是您可能正在做的事情。一个精心模块化的程序可能总是首先创建封闭类,并且它们可能会被嵌套或内部类合理地分解。你不能在模块上调用new

您甚至可以将通用模式用于脚本,其中命名空间不是非常需要,只是为了好玩和练习...

#!/usr/bin/env ruby

class A
  class Realwork_A
    ...
  end
  class Realwork_B
    ...
  end

  def run
    ...
  end
  
  self
end.new.run

【讨论】:

拜托,非常拜托,不要把它称为内部类。它不是。 B不是A 类中。 constant B 在类A 内部命名空间,但是B 引用的对象(在这种情况下恰好是一个类)和引用的类之间绝对没有关系A. 好的,“内部”术语已删除。好点子。对于那些不遵循上述论点的人来说,争论的原因是当你在 Java 中做这样的事情时,内部类的对象(这里我使用的是规范的术语)包含对外部类和外部实例变量可以被内部类方法引用。除非您将它们与代码链接起来,否则这些都不会在 Ruby 中发生。而且您知道,如果该代码存在于,咳咳,封闭类中,那么我敢打赌您可以合理地将 Bar 称为 内部类。 对我来说,当我在模块和类之间做出决定时,这句话对我帮助最大:You can't call new on a module.——所以从基本的角度来说,如果我只想命名一些类而实际上不需要创建外部“类”的 instance ,然后我将使用外部模块。但是,如果我想实例化包装/外部“类”的实例,那么我会将其设为类而不是模块。至少这对我来说是有意义的。 @FireDragon 或者另一个用例可能是您想要一个工厂类,子类继承自该类,并且您的工厂负责创建子类的实例。在这种情况下,您的 Factory 不能成为模块,因为您不能从模块继承,因此它是您不实例化的父类(如果您愿意,可以“命名空间”它的子类,有点像模块)【参考方案2】:

在 2.5 之前的 Ruby 中,嵌套类和嵌套模块之间还有另一个区别,其他答案未能涵盖,我觉得这里必须提到。这是查找过程。 简而言之:由于 Ruby 2.5 之前的***常量查找,如果您使用嵌套类,Ruby 可能最终会在错误的位置(特别是在Object)中查找您的嵌套类。在 Ruby 2.5 之前的版本中: 嵌套类结构: 假设您有一个类X,嵌套类YX::Y。然后你有一个名为Y 的***类。如果 X::Y 未加载,则在调用 X::Y 时会发生以下情况: 在X 中没有找到Y,Ruby 将尝试在X 的祖先中查找它。因为X是一个类而不是一个模块,所以它有祖先,其中有[Object, Kernel, BasicObject]。因此,它尝试在Object 中查找Y,并在其中成功找到它。但它是***Y 而不是X::Y。 您将收到以下警告:

warning: toplevel constant Y referenced by X::Y

嵌套模块结构: 假设在前面的例子中 X 是一个模块而不是一个类。一个模块只有自己作为祖先:X.ancestors 会产生 [X]。 在这种情况下,Ruby 将无法在X 的祖先之一中查找Y,并会抛出NameError。 Rails(或任何其他具有自动加载功能的框架)将在此之后尝试加载X::Y。 有关更多信息,请参阅这篇文章:https://blog.jetbrains.com/ruby/2017/03/why-you-should-not-use-a-class-as-a-namespace-in-rails-applications/ 在 Ruby 2.5 中: 移除了***常量查找。您可以使用嵌套类而不必担心遇到此错误。

【讨论】:

【参考方案3】:

其他 OOP 语言有inner classes,如果不绑定到上层类,就无法实例化。例如,在 Java 中,

class Car 
    class Wheel  

只有Car 类中的方法可以创建Wheels。

Ruby 没有这种行为。

在 Ruby 中,

class Car
  class Wheel
  end
end

不同于

class Car
end

class Wheel
end

仅以 WheelCar::Wheel 类的名称。这种名称上的差异可以让程序员明确知道Car::Wheel 类只能表示汽车车轮,而不是一般车轮。在 Ruby 中嵌套类定义是一个偏好问题,但它的目的是为了更强有力地执行两个类之间的契约,并通过这样做传达更多关于它们及其用途的信息。

但对于 Ruby 解释器来说,这只是名称上的不同。

至于您的第二个观察,嵌套在模块内的类通常用于命名类。例如:

module ActiveRecord
  class Base
  end
end

不同于

module ActionMailer
  class Base
  end
end

虽然这不是嵌套在模块中的类的唯一用途,但它通常是最常见的。

【讨论】:

@rubyprince,我不确定你在Car.newCar::Wheel.new 之间建立关系是什么意思。在 Ruby 中,您绝对不需要初始化 Car 对象来初始化 Car::Wheel 对象,但是必须加载并执行 Car 类才能使 Car::Wheel 可用。 @Pan,您混淆了 Java 内部类 和命名空间 Ruby 类。非静态 Java 嵌套类称为内部类,它仅存在于外部类的实例中。有一个允许向外引用的隐藏字段。 Ruby 内部类只是简单的命名空间,不会以任何方式“绑定”到封闭类。 它相当于 Java static (nested) i> 类。是的,答案有很多票,但并不完全准确。 我不知道这个答案是如何获得 60 票的,更不用说被 OP 接受了。从字面上看,这里没有一个真实的陈述。 Ruby 没有像 Beta 或 Newspeak 这样的嵌套类。 CarCar::Wheel 之间绝对没有任何关系。模块(因此类)只是常量的命名空间,Ruby 中没有嵌套类或嵌套模块之类的东西。 @JörgWMittag 我明白你在说什么。我的意图不是暗示您需要一个Car 实例来实例化Car::Wheel 或者Car::Wheel 只能在Car 实例中实例化,而是Car::Wheel 的命名空间在Car 下并因此传达两者之间的额外关系。我认为我的回答旨在澄清 Ruby 的这一方面,但我不怀疑它不够。如果您提出一些修改建议,我很乐意修改帖子以澄清这一点并使答案更有用。 如果不阅读整个评论线程,此帖子具有高度误导性。【参考方案4】:

除了前面的答案:Ruby 中的模块是一个类

$ irb
> module Some end
=> nil
> Some.class
=> Module
> Module.superclass
=> Object

【讨论】:

你会更准确地说'Ruby中的类是一个模块'! Ruby 中的一切都可能是一个对象,但调用模块一个类似乎不正确:irb(main):005:0> Class.ancestors.reverse => [BasicObject, Kernel, Object, Module , 类] 这里我们来定义“是” 注意模块不能被实例化。即,不可能从模块创建对象。因此,与类不同,模块没有方法new。所以虽然你可以说模块是一个类(小写),但它们与类(大写)不同:)【参考方案5】:

您可能希望使用它来将您的类分组到一个模块中。有点像命名空间的东西。

例如Twitter gem 使用命名空间来实现这一点:

Twitter::Client.new

Twitter::Search.new

所以ClientSearch 类都位于Twitter 模块下。

如果您想查看源代码,可以在here 和here 找到这两个类的代码。

希望这会有所帮助!

【讨论】:

Twitter gem 更新链接:github.com/sferik/twitter/tree/master/lib/twitter

以上是关于何时使用嵌套类和嵌套在模块中的类?的主要内容,如果未能解决你的问题,请参考以下文章

深入kotlin - 嵌套类和内部类

Java嵌套类,内部类和外部类

在JAVA中啥是顶级类啊

java中,类走之间可以嵌套吗?

静态类和内部类的区别是啥

何时在 C++ 中的嵌套类上使用 Pimpl 模式,反之亦然?