您可以使用 clone 在 Ruby 中进行基于原型的编程。 Ruby 的 Object 类同时定义了 clone 方法和 dup 方法。 clone 和 dup 都会生成它正在复制的对象的浅表副本;也就是说,对象的实例变量被复制,而不是它们引用的对象。我将演示一个例子:
class Apple
attr_accessor :color
def initialize
@color = 'red'
end
end
apple = Apple.new
apple.color
=> "red"
orange = apple.clone
orange.color
=> "red"
orange.color << ' orange'
=> "red orange"
apple.color
=> "red orange"
注意在上面的例子中,橙色的克隆复制了苹果对象的状态(也就是实例变量),但是苹果对象引用了其他对象的地方(比如字符串对象颜色),那些引用并没有被复制.相反,apple 和 orange 都引用了同一个对象!在我们的示例中,引用是字符串对象“red”。当 orange 使用 append 方法
附带说明,赋值运算符 = 将分配一个新对象,从而破坏一个引用。这是一个演示:
class Apple
attr_accessor :color
def initialize
@color = 'red'
end
end
apple = Apple.new
apple.color
=> "red"
orange = apple.clone
orange.color
=> "red"
orange.color = 'orange'
orange.color
=> 'orange'
apple.color
=> 'red'
在上面的例子中,当我们为橙色克隆的 color 实例方法分配一个新的对象时,它不再引用与 apple 相同的对象。因此,我们现在可以在不影响 apple 的 color 方法的情况下修改 orange 的 color 方法,但是如果我们从 apple 克隆另一个对象,则该新对象将在复制的实例变量中引用与 apple 相同的对象。
dup 还会生成它正在复制的对象的浅表副本,如果您要对上面显示的 dup 进行相同的演示,您将看到它的工作方式完全相同。但是克隆和复制之间有两个主要区别。首先,正如其他人提到的, clone 复制冻结状态而 dup 没有。这是什么意思? Ruby 中的“冻结”一词是不可变的深奥术语,它本身是计算机科学中的一个命名法,这意味着某些东西是无法改变的。因此,不能以任何方式修改 Ruby 中的冻结对象;它实际上是不可变的。如果您尝试修改冻结的对象,Ruby 将引发 RuntimeError 异常。由于克隆复制冻结状态,如果您尝试修改克隆对象,它将引发 RuntimeError 异常。相反,由于 dup 不会复制冻结状态,因此不会发生此类异常,我们将演示:
class Apple
attr_accessor :color
def initialize
@color = 'red'
end
end
apple = Apple.new
apple.frozen?
=> false
apple.freeze
apple.frozen?
=> true
apple.color = 'crimson'
RuntimeError: can't modify frozen Apple
apple.color << ' crimson'
=> "red crimson" # we cannot modify the state of the object, but we can certainly modify objects it is referencing!
orange = apple.dup
orange.frozen?
=> false
orange2 = apple.clone
orange2.frozen?
=> true
orange.color = 'orange'
=> "orange" # we can modify the orange object since we used dup, which did not copy the frozen state
orange2.color = 'orange'
RuntimeError: can't modify frozen Apple # orange2 raises an exception since the frozen state was copied via clone
其次,更有趣的是,克隆复制了单例类(以及它的方法)!如果您希望在 Ruby 中进行基于原型的编程,这将非常有用。首先,让我们证明单例方法确实是用 clone 复制的,然后我们可以将它应用到 Ruby 中基于原型的编程示例中。
class Fruit
attr_accessor :origin
def initialize
@origin = :plant
end
end
fruit = Fruit.new
=> #<Fruit:0x007fc9e2a49260 @origin=:plant>
def fruit.seeded?
true
end
2.4.1 :013 > fruit.singleton_methods
=> [:seeded?]
apple = fruit.clone
=> #<Fruit:0x007fc9e2a19a10 @origin=:plant>
apple.seeded?
=> true
如您所见,水果对象实例的单例类被复制到克隆中。因此克隆的对象可以访问单例方法:seeded?。但 dup 并非如此:
apple = fruit.dup
=> #<Fruit:0x007fdafe0c6558 @origin=:plant>
apple.seeded?
=> NoMethodError: undefined method `seeded?'
现在在基于原型的编程中,您没有扩展其他类的类,然后创建其方法派生自用作蓝图的父类的类的实例。相反,您有一个基础对象,然后您从该对象创建一个新对象,并复制其方法和状态(当然,由于我们通过克隆进行浅拷贝,因此实例变量引用的任何对象都将像在 JavaScript 中一样共享原型)。然后,您可以通过填写克隆方法的详细信息来填写或更改对象的状态。在下面的示例中,我们有一个基础水果对象。所有水果都有种子,所以我们创建了一个方法 number_of_seeds。但是苹果只有一个种子,所以我们创建一个克隆并填写细节。现在,当我们克隆苹果时,我们不仅克隆了方法,还克隆了状态!记住 clone 对状态(实例变量)做了一个浅拷贝。正因为如此,当我们克隆苹果得到一个 red_apple 时,red_apple 将自动拥有 1 个种子!您可以将 red_apple 视为继承自 Apple 的对象,而 Apple 又继承自 Fruit。因此,这就是我将 Fruit 和 Apple 大写的原因。我们取消了由克隆提供的类和对象之间的区别。
Fruit = Object.new
def Fruit.number_of_seeds=(number_of_seeds)
@number_of_seeds = number_of_seeds
end
def Fruit.number_of_seeds
@number_of_seeds
end
Apple = Fruit.clone
=> #<Object:0x007fb1d78165d8>
Apple.number_of_seeds = 1
Apple.number_of_seeds
=> 1
red_apple = Apple.clone
=> #<Object:0x007fb1d892ac20 @number_of_seeds=1>
red_apple.number_of_seeds
=> 1
当然,在基于原型的编程中我们可以有一个构造方法:
Fruit = Object.new
def Fruit.number_of_seeds=(number_of_seeds)
@number_of_seeds = number_of_seeds
end
def Fruit.number_of_seeds
@number_of_seeds
end
def Fruit.init(number_of_seeds)
fruit_clone = clone
fruit_clone.number_of_seeds = number_of_seeds
fruit_clone
end
Apple = Fruit.init(1)
=> #<Object:0x007fcd2a137f78 @number_of_seeds=1>
red_apple = Apple.clone
=> #<Object:0x007fcd2a1271c8 @number_of_seeds=1>
red_apple.number_of_seeds
=> 1
最终,使用克隆,您可以获得类似于 JavaScript 原型的行为。