8

我有一个典型的 OO 模式:一个基本抽象类(定义抽象方法)和几个以特定于类的方式实现这些抽象方法的类。

我习惯于在抽象方法中只编写一次文档,然后它会自动传播到几个具体的类(至少它在 Javadoc、Scaladoc、Doxygen 中的工作方式如下),即我不需要重复相同的描述在所有具体的类中。

但是,我找不到如何在 YARD 中进行这种传播。我试过了,例如:

# Some description of abstract class.
# @abstract
class AbstractClass
  # Some method description.
  # @return [Symbol] some return description
  # @abstract
  def do_something
    raise AbstractMethodException.new
  end
end

class ConcreteClass < AbstractClass
  def do_something
    puts "Real implementation here"
    return :foo
  end
end

我得到什么:

  • 代码按预期工作 - 即在抽象类中调用 throws AbstractMethodException,在具体类中完成工作
  • 在 YARD 中,AbstractClass明确定义为抽象,ConcreteClass是正常的
  • 方法描述和返回类型在AbstractClass
  • 据说方法AbstractMethodException投入AbstractClass
  • 方法根本没有描述和通用Object返回类型ConcreteClass,在基类中没有一个抽象方法存在的通知。

我期望得到的:

  • 方法的描述和返回类型被继承(即复制)到ConcreteClassfrom info atAbstractClass
  • 理想情况下,此方法在描述的“继承”或“实现”部分中指定,并带有一些来自toConcreteClass的参考链接。ConcreteClass#do_somethingAbstractMethod#do_something

有可能这样做吗?

4

2 回答 2

4

我认为问题归结为您正在尝试做的事情。看起来您正在尝试在 Ruby 中实现一个接口,如果您来自 Java 或 .NET,这很有意义,但这并不是 Ruby 开发人员真正倾向于工作的方式。

以下是关于 Ruby 中接口的典型想法的一些信息:什么是 Ruby 中的 java 接口等价物?

也就是说,我理解你想要做什么。如果您不希望您的 AbstractClass 直接实现,但您想定义可以在行为类似于 AbstractClass 规定的类中使用的方法(如在Design by Contract中),那么您可能想要使用模块。模块可以很好地保持您的代码干燥,但它们并不能完全解决与记录覆盖方法相关的问题。因此,在这一点上,我认为您可以重新考虑如何处理文档,或者至少以更 Ruby 式的方式处理它。

Ruby 中的继承实际上(通常根据我自己的经验)仅用于以下几个原因:

  • 可重用的代码和属性
  • 默认行为
  • 专业化

显然还有其他边缘情况,但老实说,这正是 Ruby 中倾向于使用的继承。这并不意味着您正在做的事情不起作用或违反某些规则,它只是在 Ruby(或大多数动态类型语言)中不常见。这种非典型行为可能是 YARD(和其他 Ruby 文档生成器)不符合您期望的原因。也就是说,从代码的角度来看,创建一个只定义必须存在于子类中的方法的抽象类实际上对您几乎没有什么好处。未定义的方法无论如何都会导致 NoMethodError 异常被抛出,并且您可以通过编程方式检查对象是否会响应方法调用(或任何消息)从调用该方法的任何方法,使用#respond_to?(:some_method)(或其他用于获取元数据的反射工具东西)。一切都回来了 Ruby'鸭打字

对于纯文档,为什么要记录您实际不使用的方法?您不应该真正关心通过调用方法发送或接收的对象的类,而只关心这些对象响应的内容。因此,如果它在这里没有增加真正的价值,请不要首先创建您的 AbstractClass。如果它包含您实际上将直接调用而不覆盖的方法,则创建一个模块,在那里记录它们,并运行$ yardoc --embed-mixins以包含在混合模块中定义的方法(及其描述)。否则,记录您实际实现它们的方法,因为每个实现应该是不同的(否则为什么要重新实现它)。

以下是我会做的类似于你正在做的事情:

# An awesome Module chock-full of reusable code
module Stuff
  # A powerful method for doing things with stuff, mostly turning stuff into a Symbol
  def do_stuff(thing)
    if thing.kind_of?(String)
      return thing.to_sym
    else
      return thing.to_s.to_sym
    end
  end
end

# Some description of the class
class ConcreteClass
  include Stuff

  # real (and only implementation)
  def do_something
    puts "Real implementation here"
    return :foo
  end
end

an_instance = ConcreteClass.new
an_instance.do_somthing       # => :foo
# > Real implementation here
an_instance.do_stuff("bar")   # => :bar

运行 YARD(使用 --embed-mixins)将包括从 Stuff 模块中混入的方法(以及它们的描述),您现在知道包括 Stuff 模块在内的任何对象都将具有您期望的方法。

您可能还想查看Ruby Contracts,因为它可能更接近您正在寻找的绝对强制方法接受并仅返回您想要的对象类型,但我不确定这将如何与 YARD 一起使用.

于 2013-10-23T17:24:23.353 回答
1

不理想,但您仍然可以使用该(see ParentClass#method)构造(在此处记录)。不理想,因为您必须为每个覆盖方法手动键入。

话虽如此,我不是 Yard 专家,但鉴于其特别可定制的架构,我会感到惊讶的是,仅通过扩展 Yard 来实现您需要的东西并不容易,我猜是在模板部门的某个地方。

于 2015-02-26T14:19:21.263 回答