TL;DR:最常规和最好的方法是使用包含基类及其子类的模块。但让我们回顾一下一切。
这方面的官方消息并不多。然而,使用模块来包含库、代码和类组是一种风格。
超类中的子类
优点:
缺点:
在超类中包含子类实际上取决于情况。但是,这种方法的任何好处也可以通过模块方法来实现。但在实践中,这还没有完成。类在这里包含方法、实例变量、类方法等。但是类可以被认为是嵌套的最后一层——除非是非常特殊的情况,否则类中没有类。
我认为这是有意义的一种情况是,使用子类的唯一方法是通过超类,例如Formatter
具有内部子类的类XML
,如PDF
, 等。假设您仅通过做事来使用这些类喜欢Formatter.new(:xml)
。但是如果我们这样做,子类应该是私有的,并且无论如何都不能被外部世界访问。在这一点上,继承是一种非常 C++y 的方式,而不是 Rubyish。
模块外的基类,模块内的子类
优点:
缺点:
- 暗示未连接:如果 Element 与其子项不在同一个命名空间中,那么除了名称之外,还有什么告诉我它甚至是相关的?
这种方法非常不自然。它使它看起来好像Element
与它的子级无关,或者如果换个角度看,它的子级是不需要处理的内部实现细节。无论哪种方式,它看起来都像是破旧、草率的命名和糟糕的代码结构规划。如果我正在使用它来阅读代码,我必须查看Elements
模块的内容以查看它Element
是否被子类化——这不是最自然的事情。
模块中的类和子类(最佳解决方案)
优点:
- 包含:超类和所有元素类都包含在一个命名空间中,允许它们轻松导入、需要、迭代等。也有助于元编程。
- 可包含性:这些类可以很容易地
include
编辑到任何代码中。
- 清晰:与子类之间有明显的关联
Element
。它们显然是一组功能。
缺点:
- 鼓励懒惰的命名:这确实鼓励你给类命名,这样的东西
Base
非常模棱两可。
这是最好的方法。它使类成为一个整洁的捆绑包,同时仍然显示出明显的关联和明显的“在这里,使用我的Div
类”(与类中的子类策略相反)。此外,这对元编程非常有帮助,在元编程中,将所有东西都放在一个模块中对于使事情正常进行至关重要。最后,这适用于 , , 等结构autoload
。require_relative
这些include
表明这是该语言的设计使用方式。
强制命名约定
优点:
- 简单:这里没有复杂性。
- 消除歧义:从短名称中消除歧义,例如
Div
或Para
通过将它们转换为DivElement
and ParaElement
。
缺点:
- 陈旧的:分组类或方法的命名约定应该只存在于没有更好方法的语言中,例如 C 或 Objective-C。C++ 在获得命名空间后立即将其删除。
- 没有程序分组:这些命名约定虽然对人类来说很清楚,但使类结构对元编程代码非常模糊,并且使程序无法将类作为一个组来处理
- 污染全局命名空间:这会在全局命名空间中创建很多很多名称,这总是一个坏主意。
这是一个非常非常糟糕的解决方案。它鼓励编写草率的、缺乏组织和意义的 C 风格代码。这些命名约定只能用在没有更好解决方案的语言中,而 Ruby 有很多更好的解决方案。即使在数组中定义所有类也比命名约定要好。
注意:但是,如果你真的想要,你可以为短名称定义一个命名约定,Div
只要Para
你仍然将它们保留在一个模块中,这样它就是Elements::DivElement
. 但是,这违反了 DRY,我不建议这样做。
结论
所以,你真的有两个选择。只需将所有内容放在一个模块中:
module Elements
class Element; end
class Div < Element; end
#etc...
end
或者,将所有内容放在具有命名约定的模块中:
module Elements
class Element; end
class DivElement < Element; end
#etc...
end
出于清晰、使用标准方法和元编程的原因,我建议使用前者。