在Ruby的文档的dup
说:
在一般情况下,
clone
和dup
可能在派生类不同的语义。 虽然clone
被用来复制对象,包括其内部状态,dup
通常使用的类对象的派生来创建新实例。
但是,当我做了一些测试,我发现他们实际上是相同的:
class Test
attr_accessor :x
end
x = Test.new
x.x = 7
y = x.dup
z = x.clone
y.x => 7
z.x => 7
那么,这两种方法之间的差异?
在Ruby的文档的dup
说:
在一般情况下,
clone
和dup
可能在派生类不同的语义。 虽然clone
被用来复制对象,包括其内部状态,dup
通常使用的类对象的派生来创建新实例。
但是,当我做了一些测试,我发现他们实际上是相同的:
class Test
attr_accessor :x
end
x = Test.new
x.x = 7
y = x.dup
z = x.clone
y.x => 7
z.x => 7
那么,这两种方法之间的差异?
子类可以重写这些方法以提供不同的语义。 在Object
本身,有两个关键区别。
首先, clone
拷贝的单例类,而dup
没有。
o = Object.new
def o.foo
42
end
o.dup.foo # raises NoMethodError
o.clone.foo # returns 42
其次, clone
保持冻结状态,而dup
没有。
class Foo
attr_accessor :bar
end
o = Foo.new
o.freeze
o.dup.bar = 10 # succeeds
o.clone.bar = 10 # raises RuntimeError
在Rubinius中实现这些方法往往是我这些问题的答案来源,因为这是很清楚的,并且一个相当标准的Ruby实现。
当ActiveRecord的处理有一个显著差异太大:
dup
创建不其ID的新对象被设置,这样你就可以通过点击一个新的对象保存到数据库.save
category2 = category.dup
#=> #<Category id: nil, name: "Favorites">
clone
创建具有相同ID的新对象,因此新对象所做的所有更改会覆盖,如果击中原始记录.save
category2 = category.clone
#=> #<Category id: 1, name: "Favorites">
一个不同之处是冻结对象。 的clone
冷冻对象也被冻结(而dup
一个冻结对象的不是)。
class Test
attr_accessor :x
end
x = Test.new
x.x = 7
x.freeze
y = x.dup
z = x.clone
y.x = 5 => 5
z.x = 5 => TypeError: can't modify frozen object
另一个区别是单方法。 这里同样的故事, dup
不会复制那些,但clone
一样。
def x.cool_method
puts "Goodbye Space!"
end
y = x.dup
z = x.clone
y.cool_method => NoMethodError: undefined method `cool_method'
z.cool_method => Goodbye Space!
两者都几乎相同,但克隆确实比DUP一两件事。 在克隆对象的冻结状态也被复制。 在DUP,它会永远被解冻。
f = 'Frozen'.freeze
=> "Frozen"
f.frozen?
=> true
f.clone.frozen?
=> true
f.dup.frozen?
=> false
在新的文档包含一个很好的例子:
class Klass
attr_accessor :str
end
module Foo
def foo; 'foo'; end
end
s1 = Klass.new #=> #<Klass:0x401b3a38>
s1.extend(Foo) #=> #<Klass:0x401b3a38>
s1.foo #=> "foo"
s2 = s1.clone #=> #<Klass:0x401b3a38>
s2.foo #=> "foo"
s3 = s1.dup #=> #<Klass:0x401b3a38>
s3.foo #=> NoMethodError: undefined method `foo' for #<Klass:0x401b3a38>
你可以用克隆做在Ruby中基于原型的编程。 Ruby的Object类定义了clone方法和DUP方法。 这两个克隆和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"
在上述例子中的通知,橙色克隆拷贝的状态(即,实例变量)苹果对象的,但其中苹果对象引用其它对象(如字符串对象的颜色),这些参考文献不被复制。 相反,苹果和橘子都引用同一个对象! 在我们的例子中,参考的是字符串对象“红色”。 当橙色使用append方法,<<,修改现有的字符串对象,它改变了字符串对象到“红色橙色”。 这实际上改变了apple.color过,因为它们都指向同一个String对象。
作为边注,赋值操作符,=,将分配一个新的对象,从而摧毁的参考。 这里是一个演示:
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'
在上面的例子中,当我们分配了一个全新的对象到橙色克隆的颜色实例方法,它不再指向同一对象的苹果。 因此,我们现在可以修改橙的颜色的方法,而不会影响苹果的色法,但如果我们从苹果克隆另一个对象,这个新对象将复制的实例变量作为苹果引用同一个对象。
DUP也会产生被复制对象的浅拷贝,如果你是做给上面所示DUP同一演示中,你会看到它的工作原理完全一样。 但也有克隆和DUP之间的两个主要区别。 首先,正如其他人所提到的,克隆拷贝冻结状态和DUP无法解决。 这是什么意思? 在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中进行基于原型的编程这是非常有用的。 首先,让我们表明,确实是单身方法与克隆复制,然后我们就可以在基于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
正如你所看到的,单身类水果的对象实例被复制到克隆。 因此克隆对象有权访问单方法:接种的? 但是,这不是与DUP的情况:
apple = fruit.dup
=> #<Fruit:0x007fdafe0c6558 @origin=:plant>
apple.seeded?
=> NoMethodError: undefined method `seeded?'
现在,在基于原型的编程,你没有这扩展其他类的类,然后创建的类,它的方法从作为蓝本父类派生的实例。 取而代之的是,你有一个基本对象,然后创建从它的方法和状态复制(当然,因为我们是通过克隆做浅复制对象的新对象,实例变量引用任何对象将如同在JavaScript共享原型)。 然后,您可以填写或通过的克隆方法的细节填充改变对象的状态。 在下面的例子中,我们有一个基础水果对象。 所有的水果都有籽,所以我们创建了一个方法number_of_seeds。 但是苹果有一个种子,所以我们创建了一个克隆,并填写详细信息。 现在,当我们克隆苹果,我们不仅克隆方法,但是我们克隆了状态! 记住克隆确实状态(实例变量)的浅表副本。 正因为如此,当我们克隆苹果获得red_apple,red_apple将自动获得1种! 你可以认为red_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
当然,我们可以在基于protoype编程构造方法:
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原型行为的东西。