在它的父类中定义子类是不合常的吗?

时间:2013-02-19 21:27:10

标签: ruby

在下面的父类下面定义一个子类是否常规?

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

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

最好使用哪种?
各自的优点和缺点是什么?

3 个答案:

答案 0 :(得分:9)

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

这方面没有多少官方消息来源;但是,使用模块来包含库,代码和类组是一种风格。

超类

中的子类

<强>赞成

  • 自包含:整个类系统包含在一个名称下

<强>缺点:

  • 不自然:谁会想到在超类中查找子类?

将子类置于超类中实际上取决于具体情况。但是,该方法的任何好处也可通过模块方法实现。但实际上,这还没有完成。类在这里包含方法,实例变量,类方法等。但是类可以被认为是最后一级嵌套 - 除非它是一个非常具体的环境,否则你不会在类中有一个类。

我认为这有意义的一种情况是使用子类的唯一方法是通过超类,例如具有内部子类的Formatter类,如XML,{ {1}}等等。我们假设您只使用PDF之类的内容来使用这些类。但是,如果我们这样做,子类应该是私有的,无论如何都不能被外界访问。在这一点上,继承是一种非常C ++的方式,而不是Rubyish。

基类外部模块,

中的子类

<强>优点:

  • 我无法想到任何

<强>缺点:

  • 意味着非连接:如果Element与其子节点不在同一个命名空间中,超出名称的内容告诉我它甚至是相关的?

这种方法非常不自然。这看起来好像Formatter.new(:xml)与它的孩子没有任何关系,或者如果看得不一样,那么它的孩子的内部实施细节是无法处理的。 。无论哪种方式,它看起来像破旧,草率命名和糟糕的代码结构规划。如果我正在使用此代码阅读代码,我必须查看Element模块的内容,看看Elements完全是子类 - 而且这不是最多的自然要做的事。

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

<强>优点:

  • 包含:超类和所有Element类都包含在一个名称空间中,允许它们轻松导入,需要,迭代等。还有助于元编程。
  • Includable:可以轻松地将类Element添加到任何代码中。
  • 明确:include和子类之间存在明显的关联。它们显然是一组功能。

<强>缺点:

  • 鼓励懒惰命名:这确实鼓励你为Element这类非常模糊的类命名。

这是最好的方法。它使类成为一个整洁的包,同时仍然显示出明显的关联和显而易见的&#34;在这里,使用我的Base类&#34; (而不是类中的子类策略)。此外,这对于元编程非常有用,其中包含模块中的所有内容对于使事情有效至关重要。最后,这适用于Divautoloadrequire_relative等构造。这表明这是设计使用语言的方式。

强制命名约定

<强>优点:

  • 简单:这里没有复杂性。
  • 消除歧义:通过将includeDiv转换为ParaDivElement等短名称,消除歧义。

<强>缺点:

  • 过时:组类或方法的命名约定只应存在于没有更好方法的语言中,例如C或Objective-C。一旦获得名称空间,C ++就会删除它。
  • 没有编程分组:这些命名约定虽然对人类很清楚,但是对于元编程代码来说,类结构非常混浊,并使程序无法作为一个组来处理类
  • 污染全局命名空间:这会在全局命名空间中创建许多名称,这总是一个坏主意。

这是非常非常糟糕的解决方案。它包含了编写草率,C风格的代码,几乎没有组织和意义。这些命名约定只能在没有更好解决方案的语言中使用,Ruby有很多更好的解决方案。即使定义数组中的所有类也比命名约定更好。

注意:但是,如果您真的想要,可以在ParaElementDiv这样的短名称上定义命名约定,只要您仍然将它们保留在模块中,这样就可以了; s Para。但是,这违反了DRY,我不会暗示它。

结论

所以,你真的有两个选择。把所有东西放在一个模块中:

Elements::DivElement

或者,将所有内容放在具有命名约定的模块中:

module Elements
  class Element; end
  class Div < Element; end
  #etc...
end

我为了清晰,使用标准方法和元编程原因而采用前者。

答案 1 :(得分:3)

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

我从来没有像在你的第一个例子中那样在其父类下定义一个子类 - 除了,因为它有时会混淆我团队中的其他开发人员。因此,根据您与谁合作以及他们的偏好,我认为这种方法没有任何问题。

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

我会避免使用像Base这样的名字,因为在我看来这种通用名称会鼓励你把任何旧东西扔进那里,而一个描述这个类所做的名字会(希望)会让你想想每次添加一个方法,如果它真的属于那里。

而且我真的不喜欢复合名称的粉丝,就像你的命名约定例子一样,我用一种模糊的感觉证明它就像数据库规范化 - 每个字段(或类名)应该只包含一个部分信息。

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

答案 2 :(得分:1)

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

原则上,您可能希望模块同时位于同一个模块的名称空间内并从其继承,但这取决于您的用例。如果不讨论特定的用例,就不能确定你提出的那种方式中哪种方式最好。

相关问题