您更新后的问题现在看起来完全不同了。如果我理解正确的话,你想加入对象分配和初始化,这与元类完全无关。 (但你仍然没有写出你真正想做的事情,所以我可能还是会离开。)
在一些面向对象的语言中,对象是由构造函数创建的。但是,Ruby 没有构造函数。构造函数只是工厂方法(有愚蠢的限制);如果您可以使用(更强大的)工厂方法来代替,那么没有理由将它们使用设计良好的语言。
Ruby 中的对象构造是这样工作的:对象构造分为两个阶段,分配和初始化。分配由名为allocate 的公共类方法完成,该方法被定义为Class 类的实例方法,并且通常从不 被覆盖。 (事实上,我认为您实际上可以覆盖它。)它只是为对象分配内存空间并设置一些指针,但是,此时对象并不真正可用.
这就是初始化器的用武之地:它是一个名为initialize的实例方法,它设置对象的内部状态并将其带入一个一致的、完全定义的状态,可供其他对象使用。
所以,为了完全创建一个新对象,你需要做的是:
x = X.allocate
x.initialize
[注意:Objective-C 程序员可能会认识到这一点。]
但是,因为很容易忘记调用initialize,并且作为一般规则,对象在构造后应该是完全有效的,所以有一个方便的工厂方法称为Class#new,它可以为您完成所有工作并且看起来像这样:
class Class
def new(*args, &block)
obj = allocate
obj.initialize(*args, &block)
return obj
end
end
[注意:实际上,initialize 是私有的,因此必须使用反射来规避这样的访问限制:obj.send(:initialize, *args, &block)]
顺便说一句,这就是构造一个对象的原因,你调用一个公共类方法Foo.new,但你实现一个私有实例方法Foo#initialize ,这似乎绊倒了很多新人。
但是,没有这一切都以任何方式融入到语言中。任何类的主要工厂方法通常称为 new 的事实只是一种约定(有时我希望它有所不同,因为它看起来类似于 Java 中的构造函数,但完全不同) .在其他语言中,构造函数必须具有特定名称。在 Java 中,它必须与类同名,这意味着 a) 只能有一个构造函数,b) 匿名类不能有构造函数,因为它们没有名称。在 Python 中,必须调用工厂方法__new__,这再次意味着只能有一个。 (在 Java 和 Python 中,您当然可以有不同的工厂方法,但调用它们看起来与调用默认值不同,而在 Ruby(以及该模式起源的 Smalltalk)中看起来完全一样。)
在 Ruby 中,可以有任意多的工厂方法,您可以使用任何您喜欢的名称,并且工厂方法可以有许多不同的名称。 (例如,对于集合类,工厂方法通常别名为[],它允许您编写List[1, 2, 3] 而不是List.new(1, 2, 3),后者看起来更像一个数组,因此强调了列表的集合性质。 )
简而言之:
- 标准化的工厂方法是
Foo.new,但它可以是任何东西
-
Foo.new 调用 allocate 为空对象 foo 分配内存
-
Foo.new 然后调用foo.initialize,即Foo#initialize 实例方法
- 所有这三个方法都与其他方法一样,您可以取消定义、重新定义、覆盖、包装、别名等等
- 好吧,除了
allocate 需要在 Ruby 运行时内分配内存,而这在 Ruby 中是无法做到的
在 Python 中,__new__ 大致对应于 Ruby 中的 new 和 allocate,而__init__ 恰好 对应于 Ruby 中的 initialize。主要区别在于,在 Ruby 中,new 调用 initialize,而在 Python 中,runtime 在 __new__ 之后自动调用 __init__。
例如,这是一个类,它最多只允许创建 2 个实例:
class Foo
def self.new(*args, &block)
@instances ||= 0
raise 'Too many instances!' if @instances >= 2
obj = allocate
obj.send(:initialize, *args, &block)
@instances += 1
return obj
end
attr_reader :name
def initialize(name)
@name = name
end
end
one = Foo.new('#1')
two = Foo.new('#2')
puts two.name # => #2
three = Foo.new('#3') # => RuntimeError: Too many instances!