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

cmh*_*bbs 134 ruby oop

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

class Foo
  class Bar
    # do some useful things
  end
end
Run Code Online (Sandbox Code Playgroud)

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

module Baz
  class Quux
    # more code
  end
end
Run Code Online (Sandbox Code Playgroud)

文档和文章都很稀疏,或者我没有足够的知识来搜索正确的搜索术语,但我似乎无法找到有关该主题的更多信息.

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

Pan*_*kos 132

其他OOP语言具有内部类,如果不绑定到上级,则无法实例化它们.例如,在Java中,

class Car {
    class Wheel { }
}
Run Code Online (Sandbox Code Playgroud)

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

Ruby没有那种行为.

在Ruby中,

class Car
  class Wheel
  end
end
Run Code Online (Sandbox Code Playgroud)

不同于

class Car
end

class Wheel
end
Run Code Online (Sandbox Code Playgroud)

只有在类的名称WheelCar::Wheel.这种名称差异可以使程序员明确表示Car::Wheel该类只能代表车轮,而不是一般的车轮.在Ruby中嵌套类定义是一个偏好的问题,但它有一个目的,即它更强有力地强制执行两个类之间的契约,并且这样做会传达有关它们及其用途的更多信息.

但是对于Ruby解释器来说,它只是名称上的差异.

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

module ActiveRecord
  class Base
  end
end
Run Code Online (Sandbox Code Playgroud)

不同于

module ActionMailer
  class Base
  end
end
Run Code Online (Sandbox Code Playgroud)

虽然这不是嵌套在模块内部的类的唯一用法,但它通常是最常见的.

  • @Pan,你混淆了Java*内部类*和命名空间的Ruby类.非静态Java嵌套类称为*内部类*,它仅存在于外部类的实例中.有一个隐藏字段允许向外引用.*Ruby内部类只是名称空间,并且不以任何方式"绑定"到封闭类.*它等同于Java*static**(嵌套)*类.是的,答案有很多选票,但并不完全准确. (28认同)
  • 这篇文章在没有阅读整个评论主题的情况下极具误导性. (10认同)
  • 我不知道这个答案如何获得60票,更不用说被OP接受了.这里确实没有一个真实的陈述.Ruby没有像Beta或Newspeak这样的嵌套类."Car"和"Car :: Wheel"之间绝对没有任何关系.模块(以及类)只是常量的命名空间,Ruby中没有嵌套类或嵌套模块. (6认同)
  • @rubyprince,我不确定你在'Car.new`和`Car :: Wheel.new`之间建立关系是什么意思.你绝对不需要初始化一个`Car`对象来初始化Ruby中的`Car :: Wheel`对象,但必须加载并执行`Car`类才能使`Car :: Wheel`可用. (5认同)
  • 两者之间的唯一区别是恒定分辨率(这是词法,因此明显不同,因为两个片段在词汇上是不同的).但是,关于所涉及的课程,两者之间绝对没有区别.只有两个完全不相关的类.期.Ruby没有嵌套/内部类.您对嵌套类的定义是正确的,但它根本不适用于Ruby,因为您可以轻松地测试:`Car :: Wheel.new`.繁荣.我刚刚构建了一个`Wheel`对象,它没有嵌套在`Car`对象中. (4认同)
  • 你是什​​么意思"绑定"在"哪个不能被实例化而不受上级课程约束"? (3认同)
  • 术语"内部类"的使用和"OOP中的内部类"和绑定的讨论令人困惑,特别是对于具有Java知识的程序员(在很大程度上同意@DigitalRoss).这两个OP的例子都只是命名空间. (3认同)
  • @JörgWMittag我明白你在说什么.我的意图并不是暗示你需要一个`Car`实例来实例化`Car :: Wheel`或者'Car :: Wheel`只能在`Car`实例中实例化,而是'Car :: Wheel`在`Car`下命名,因此在两者之间传递了一个额外的关系.我认为我的回答旨在澄清Ruby的这一方面,但我不怀疑它不足.如果您建议进行一些编辑,我很乐意修改帖子以澄清这一点并使答案更有用. (3认同)
  • @Andrew Grimm,我的意思是你不能在不调用高级类(`Car`)的情况下实例化低级类(`Car :: Wheel`).从这个意义上讲,它与上层阶级"绑定".您也可以将其视为"Car :: Wheel"在没有"Car"的情况下不能存在.从那个意义上说,"Car :: Wheel"必然会被"Car"所束缚. (2认同)
  • @JörgWMittag 您可能会在有关内部类、嵌套类等的术语上分心,但我认为如果您重新阅读问题和答案,您会发现发布的答案确实提供了“为什么/何时”的解决方案应该使用这些技术"。此外,您可能会对 `class Car::Wheel ; 之间的差异感到困惑。end` 和 `class Car ; 类轮子;结尾 ; 结束`。 (2认同)

Dig*_*oss 48

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

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

class A; class B; end; end
A::B.new
Run Code Online (Sandbox Code Playgroud)

优点与模块的优点相同:封装,仅在一个地方使用的分组代码,以及将代码放置在更接近使用位置的位置.一个大型项目可能有一个外部模块在每个源文件中反复出现,并包含许多类定义.当各种框架和库代码都执行此操作时,它们每个只为顶级提供一个名称,从而减少冲突的可能性.平淡无奇,可以肯定,但这就是他们被使用的原因.

使用类而不是模块来定义外部命名空间可能在单文件程序或脚本中有意义,或者如果您已经使用顶级类,或者如果您实际上要添加代码以将类链接在一起真正的内在风格.Ruby没有内部类,但没有什么能阻止你在代码中创建相同的行为.从内部对象引用外部对象仍然需要从外部对象的实例中加入点,但是嵌套类将表明这是您可能正在做的事情.精心模块化的程序可能总是首先创建封闭类,并且可以合理地使用嵌套类或内部类进行分解.你不能打电话new给模块.

即使对于脚本而言,您也可以使用通用模式,其中名称空间不是非常需要的,只是为了娱乐和练习......

#!/usr/bin/env ruby

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

  def run
    ...
  end

  self
end.new.run
Run Code Online (Sandbox Code Playgroud)

  • 拜托,请相信,不要把它称为内部阶级.不是.类'B`在*A类中是*不*.*constant*`B`在类'A`中被命名空间,但是`B`引用的对象(在这种情况下恰好是一个类)和由`A`引用的类之间绝对没有关系. (15认同)
  • 好的,删除了"内部"术语.好点子.对于那些没有遵循上述论点的人,争议的原因是当你在Java中做类似的事情时,内部类的对象(这里我使用的术语是规范的)包含对外部类和外部实例变量可以由内部类方法引用.除非您将它们与代码链接,否则这些都不会发生在Ruby中.而且你知道,如果那个代码存在于ahem,封闭的类中,那么我打赌你可以合理地称Bar为*内部类.* (2认同)
  • 对我来说,在模块和类之间做出决定时,这句话对我帮助最大:“你不能在模块上调用 new。”——所以从基本术语来说,如果我只是想命名一些类而永远不需要实际上创建外部“类”的*实例*,然后我将使用外部模块。但是,如果我想实例化包装/外部“类”的实例,那么我会将其设为类而不是模块。至少这对我来说是有意义的。 (2认同)

Pab*_*dez 15

您可能希望使用它将类分组到模块中.命名空间的东西.

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

Twitter::Client.new

Twitter::Search.new
Run Code Online (Sandbox Code Playgroud)

所以这两个ClientSearch类都存在于Twitter模块之下.

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

希望这可以帮助!

  • Twitter gem更新链接:https://github.com/sferik/twitter/tree/master/lib/twitter (3认同)

Tim*_*nov 5

在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
Run Code Online (Sandbox Code Playgroud)


嵌套的模块结构: 在上一个示例中,假设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:
删除了顶级常量查询。
您可以使用嵌套类,而不必担心遇到此错误。