8

像下面这样在其父类下定义一个子类是惯例吗?

class Element

  class Div < Element

  end

  class Paragraph < Element

  end

end

还是让模块包含子类更合适?

class Element

end

module Elements

  class Div < Element

  end

  class Paragraph < Element

  end

end

或者在一个模块中创建一个“基”类并在同一个模块中定义子类?

module Element

  class Base

  end

  class Div < Base

  end

  class Paragraph < Base

  end

end

还是强制命名约定更好?

class Element

end

class DivElement < Element

end

class ParagraphElement < Element

end

似乎每个库都选择了不同的命名空间/命名约定。

哪个最好用?
各自的优缺点是什么?

4

3 回答 3

9

TL;DR:最常规和最好的方法是使用包含基类及其子类的模块。但让我们回顾一下一切。

这方面的官方消息并不多。然而,使用模块来包含库、代码和类组是一种风格。

超类中的子类

优点

  • 自成一体:整个班级系统都包含在一个名称之下

缺点:

  • 不自然:谁会想到在超类内部寻找子类?

在超类中包含子类实际上取决于情况。但是,这种方法的任何好处也可以通过模块方法来实现。但在实践中,这还没有完成。类在这里包含方法、实例变量、类方法等。但是类可以被认为是嵌套的最后一层——除非是非常特殊的情况,否则类中没有类。

我认为这是有意义的一种情况是,使用子类的唯一方法是通过超类,例如Formatter具有内部子类的类XML,如PDF, 等。假设您仅通过做事来使用这些类喜欢Formatter.new(:xml)。但是如果我们这样做,子类应该是私有的,并且无论如何都不能被外部世界访问。在这一点上,继承是一种非常 C++y 的方式,而不是 Rubyish。

模块外的基类,模块内的子类

优点:

  • 我想不出任何

缺点:

  • 暗示未连接:如果 Element 与其子项不在同一个命名空间中,那么除了名称之外,还有什么告诉我它甚至是相关的?

这种方法非常不自然。它使它看起来好像Element与它的子级无关,或者如果换个角度看,它的子级是不需要处理的内部实现细节。无论哪种方式,它看起来都像是破旧、草率的命名和糟糕的代码结构规划。如果我正在使用它来阅读代码,我必须查看Elements模块的内容以查看它Element是否被子类化——这不是最自然的事情。

模块中的类和子类(最佳解决方案)

优点:

  • 包含:超类和所有元素类都包含在一个命名空间中,允许它们轻松导入、需要、迭代等。也有助于元编程。
  • 可包含性:这些类可以很容易地include编辑到任何代码中。
  • 清晰:与子类之间有明显的关联Element。它们显然是一组功能。

缺点:

  • 鼓励懒惰的命名:这确实鼓励你给类命名,这样的东西Base非常模棱两可。

这是最好的方法。它使类成为一个整洁的捆绑包,同时仍然显示出明显的关联和明显的“在这里,使用我的Div类”(与类中的子类策略相反)。此外,这对元编程非常有帮助,在元编程中,将所有东西都放在一个模块中对于使事情正常进行至关重要。最后,这适用于 , , 等结构autoloadrequire_relative这些include表明这是该语言的设计使用方式。

强制命名约定

优点:

  • 简单:这里没有复杂性。
  • 消除歧义:从短名称中消除歧义,例如DivPara通过将它们转换为DivElementand 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

出于清晰、使用标准方法和元编程的原因,我建议使用前者。

于 2013-02-23T16:12:59.030 回答
3

这是我多次遇到的问题——你有一些共享功能和几个不同的实现类使用它——共享功能和实现类的命名空间具有相同的名称是很自然的。

在您的第一个示例中,我从来没有遇到过在其父类下定义子类的问题 -除了它有时会混淆我团队中的其他开发人员的事实。因此,取决于您与谁一起工作以及他们的偏好是什么,我认为这种方法没有任何问题。

如果有一个有意义的名称,我仍然更喜欢第二种方法,命名空间使用不同的名称。如果没有,我会使用第一种方法。

Base我会避免使用像它真的属于那里。

而且我真的不喜欢您的命名约定示例中的复合名称,我用一种模糊的感觉向自己证明它就像数据库规范化 - 每个字段(或类名)应该只包含一条信息。

无论如何,我希望这对你有帮助。除了我自己的经验之外,我无法真正从任何来源中汲取经验(由您决定是否认为这是“可信的”),因为我确实认为这个问题没有绝对的答案。这在很大程度上是主观的。

于 2013-02-23T15:00:08.723 回答
1

命名空间和继承用于不同的目的。使用名称空间将一个模块封装在另一个模块中。使用继承来定义一个模块,使用另一个的方法/变量/常量作为默认值。

原则上,您可能希望一个模块既位于同一个模块的名称空间内又继承自同一个模块,但这取决于您的用例。如果不讨论特定的用例,就无法确定您提供的方法中哪种方法是最好的。

于 2013-02-19T21:55:45.973 回答