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


144

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

class Foo
  class Bar
    # do some useful things
  end
end

以及嵌套在模块中的类如下:

module Baz
  class Quux
    # more code
  end
end

文档和文章都很稀少,或者我对这个主题的教育不足,无法摸索正确的搜索词,但是我似乎找不到关于该主题的太多信息。

有人可以提供示例或链接到有关为什么/何时使用这些技术的帖子吗?

Answers:


140

其他OOP语言具有内部类,这些内部类必须绑定到上层类才能实例化。例如,在Java中,

class Car {
    class Wheel { }
}

只有Car类中的方法才能创建Wheel

Ruby没有这种行为。

在Ruby中

class Car
  class Wheel
  end
end

不同于

class Car
end

class Wheel
end

仅以类Wheelvs. 的名义Car::Wheel。名称上的这种差异可以向程序员表明,Car::Wheel该类只能代表一个车轮,而不是一个普通的车轮。在Ruby中嵌套类定义是一个优先事项,但在某种意义上说,它可以更严格地在两个类之间强制执行契约,并以此传达有关它们及其用途的更多信息。

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

关于第二个观察,嵌套在模块内部的类通常用于为这些类命名空间。例如:

module ActiveRecord
  class Base
  end
end

不同于

module ActionMailer
  class Base
  end
end

尽管这不是嵌套在模块内部的类的唯一用途,但通常是最常见的。


5
@rubyprince,我不确定在Car.new和之间建立关系是什么意思Car::Wheel.new。您绝对不需要初始化Car对象即可Car::Wheel在Ruby中初始化对象,但是Car必须加载并执行该类才能Car::Wheel使用。
Pan Thomakos 2011年

30
@Pan,您在混淆Java 内部类和命名空间Ruby类。非静态Java嵌套类称为内部类,它仅存在于外部类的实例中。有一个允许外向引用的隐藏字段。Ruby内部类仅是命名空间,并不以任何方式“绑定”到封闭的类。它等效于Java 静态 (嵌套)类。是的,答案有很多票,但并不完全正确。
DigitalRoss 2014年

7
我不知道这个答案如何获得60票赞成,更不用说被OP接受了。实际上,这里没有一个真正的陈述。Ruby没有像Beta或Newspeak这样的嵌套类。Car和之间绝对没有任何关系Car::Wheel。模块(以及类)只是常量的名称空间,在Ruby中没有嵌套类或嵌套模块之类的东西。
约尔格W¯¯米塔格

4
两者之间的唯一区别是常数分辨率(这是词汇上的,因此明显不同,因为两个摘要在词汇上是不同的)。但是,关于所涉及的类,两者之间绝对没有区别。只有两个完全不相关的类。期。Ruby没有嵌套/内部类。您的嵌套类的定义是正确的,但它根本不适用于Ruby中,你可以平凡测试:Car::Wheel.new。繁荣。我刚刚构造了一个Wheel不嵌套在Car对象内部的对象。
约尔格W¯¯米塔格

10
如果不阅读整个评论主题,则该帖子具有很高的误导性。
内森

50

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

公认的答案对任何事情都不正确。1在下面的示例中,我创建了一个词法包围类的实例,而该类没有一个实例。

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

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

在一个文件程序或脚本中,或者如果您已经将顶级类用于某些东西,或者您实际上要添加代码以将这些类链接在一起,则使用类而不是模块来定义外部名称空间可能很有意义。以真正的内在阶级风格。Ruby没有内部类,但是没有什么可以阻止您在代码中创建相同的行为。从内部对象引用外部对象仍然需要从外部对象的实例中点入,但是嵌套类将表明这就是您可能正在做的事情。一个经过仔细模块化的程序可能总是总是首先创建封闭类,并且它们可能会被嵌套类或内部类合理地分解。您无法调用new模块。

您甚至可以将通用模式用于脚本,在脚本中并不需要名称空间,只是为了娱乐和练习...

#!/usr/bin/env ruby

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

  def run
    ...
  end

  self
end.new.run

15
拜托,漂亮拜托,不要把它称为内部类。不是。该班B不是内部类A。该常量 B在类内部命名空间A,但是所引用的对象B(在本例中恰好是一个类)与所引用的类之间绝对没有任何关系A
约尔格W¯¯米塔格

2
好的,删除“内部”术语。好点子。对于那些不遵循上述论点的人,引起争议的原因是,当您在Java中执行此类操作时,内部类的对象(在这里我使用的是规范性的术语)包含对外部类和外部实例变量可以由内部类方法引用。除非您将它们与代码链接,否则在Ruby中这些都不会发生。而且您知道,如果该代码存在于封闭类中,那么我敢打赌,您可以合理地将Bar称为内部类。
DigitalRoss

1
对我来说,在决定模块和类之间的关系时,这条语句对我的帮助最大:You can't call new on a module.-从基本的角度来说,如果我只想为某些类命名,而无需实际创建外部“类” 的实例,则我会使用一个外部模块。但是,如果我要实例化包装/外部“类”的实例,则可以将其设为类而不是模块。至少这对我来说很有意义。
FireDragon

@FireDragon或另一个用例可能是您想要一个继承自其子类的Factory类,并且您的工厂负责创建这些子类的实例。在那种情况下,您的Factory不能是模块,因为您不能从模块继承,所以它是您不实例化的父类(如果需要,可以“命名空间”它的子级,有点像模块)
rmcsharry

15

您可能想使用它将您的类分组为一个模块。某种名称空间的东西。

例如,Twitter gem使用命名空间来实现此目的:

Twitter::Client.new

Twitter::Search.new

因此,ClientSearch类都生活在该Twitter模块下。

如果要检查源代码,可以在此处此处找到两个类的代码。

希望这可以帮助!



6

在2.5之前的Ruby中,嵌套类和嵌套模块之间还有另一个区别,其他答案未能解决,我认为必须在这里提及。这是查找过程。

简而言之:由于Ruby在2.5之前的顶级常量查询,Object如果您使用嵌套类,Ruby可能最终会在错误的位置(特别是)寻找嵌套类。

在2.5之前的Ruby中:
嵌套类结构: 假设您有一个X带有嵌套类Y或的类X::Y。然后,您还具有一个名为的顶级类Y。如果X::Y没有加载,那么下面,当你调用情况X::Y

已经没有发现YX,红宝石会尝试看看它的祖先X。以来X是类而不是模块,它有祖先,其中有[Object, Kernel, BasicObject]。因此,它将尝试YObject中找到成功的位置。

然而,这是最高级别Y,而不是X::Y。您将收到以下警告:

warning: toplevel constant Y referenced by X::Y


嵌套的模块结构: 在前面的示例中,假设X是模块而不是类。

一个模块只有其自身是祖先:X.ancestors将产生[X]

在这种情况下,Ruby将无法Y在的祖先之一中寻找X并将抛出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

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

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

11
您会更准确地说“ Ruby中的类是模块”!
Tim Diggins 2014年

2
Ruby中的所有内容都可能是对象,但将模块称为类似乎不正确:irb(main):005:0> Class.ancestors.reverse => [BasicObject,内核,对象,模块,类]
乍得

这就是“ is”的定义
ahnbizcad

请注意,模块无法实例化。即,不可能从模块创建对象。因此,与类不同,模块没有方法new。因此,尽管您可以说模块是一个类(小写),但它们与类(大写)是不同的:)
rmcsharry19年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.