奇怪的Ruby类初始化逻辑?

时间:2017-04-25 09:16:25

标签: ruby constructor

我正在我的应用程序中集成的一些开源代码包含一些包含相应代码的类:

class SomeClass < SomeParentClass

  def self.new(options = {})
    super().tap { |o|
      # do something with `o` according to `options`
    }
  end

  def initialize(options = {})
    # initialize some data according to `options`
  end

end

据我了解,self.newinitialize都做同样的事情 - 后者“在施工期间”而前者“施工后”,它看起来像一个可怕的要使用的模式 - 为什么要将对象初始化分成两部分,其中一部分显然是“错误思考(tm)”?

3 个答案:

答案 0 :(得分:4)

理想情况下,我想查看super().tap { |o|块内的内容,因为虽然这看起来很糟糕,但可能只是在调用initialize之前或之后需要进行一些交互。

如果没有上下文,你可能只是在看一些有效但在Ruby中不被认为是好习惯的东西。

但是,也许单独的self.newinitialize方法的方法允许框架设计者实现框架的子类能够部分,并且仍然确保框架所需的设置完成而没有稍微笨拙的文档这需要特定使用super()。如果最终用户只使用子类class MyClass < FrameworkClass获得他们期望的功能并且没有一些额外的注释,那么文档和更清晰的API会稍微容易一些:

  

当您实现子类initialize时,请记住在开始时放置super,否则魔法将无效

。 。 。我个人觉得这个设计有问题,但我认为至少会有明确的动机。

可能有更深层次的Ruby语言使代码在自定义self.new块中运行 - 例如,它可能允许构造函数在返回之前切换或更改特定对象(甚至返回不同类的对象) 。但是,我很少看到这些事情在实践中完成,几乎总有一些其他方法可以实现此类代码的目标,而无需自定义new

评论中提出的自定义/不同Class.new方法示例:

后者可以通过不同的ORM设计来继承(尽管所有这些方案都有利弊)。

第一个(Structs)是该语言的核心,因此必须像现在这样工作(尽管设计师可以选择不同的方法名称)。

答案 1 :(得分:3)

如果没有看到代码的其余部分,就无法确定代码的原因。

但是,我想问一下你的问题:

  

据我了解,self.newinitialize都做同样的事情 - 后者#34;施工期间&#34;和前一个&#34;施工后#34;

他们没有做同样的事情。

Ruby中的对象构造分两步执行:Class#allocate从对象空间分配一个新的空对象,并将其内部类指针设置为self。然后,使用一些默认值初始化空对象。通常,此初始化由名为initialize的方法执行,但这只是一种约定;可以随意调用该方法。

还有一个名为Class#new的辅助方法除了执行程序员的方便之外什么都不做,只执行两个步骤:

class Class
  def new(*args, &block)
    obj = allocate
    obj.send(:initialize, *args, &block)
    obj
  end

  def allocate
    obj = __MagicVM__.__allocate_an_empty_object_from_the_object_space__
    obj.__set_internal_class_pointer__(self)
    obj
  end
end

class BasicObject
  private def initialize(*) end
end

答案 2 :(得分:2)

构造函数 new必须是类方法,因为您从没有实例的地方开始;你不能在特定的实例上调用该方法。另一方面,初始化例程initialize最好定义为实例方法,因为您希望专门针对某个实例执行某些操作。因此,Ruby旨在通过类方法initialize在新实例创建后立即在内部调用实例方法new