您更新的问题现在看起来完全不同。如果我理解正确的话,你想加入对象分配和初始化,这与元类完全无关。(但你仍然没有写出你真正想做的事情,所以我可能还是会离开。)
在一些面向对象的语言中,对象是由构造函数创建的。但是,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
在 Ruby 中则__init__
完全对应initialize
。主要区别在于,在 Ruby 中new
调用initialize
,而在 Python 中,运行时自动调用__init__
after __new__
。
例如,这是一个最多只允许创建 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!