dup的Ruby文档说:
通常,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
那么这两种方法有什么不同呢?
dup的Ruby文档说:
通常,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
那么这两种方法有什么不同呢?
当前回答
一个不同之处在于冷冻物体。冻结对象的克隆也被冻结(而冻结对象的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!
其他回答
您可以使用克隆在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"
Notice in the above example, the orange clone copies the state (that is, the instance variables) of the apple object, but where the apple object references other objects (such as the String object color), those references are not copied. Instead, apple and orange both reference the same object! In our example, the reference is the string object 'red'. When orange uses the append method, <<, to modify the existing String object, it changes the string object to 'red orange'. This in effect changes apple.color too, since they are both pointing to the same String object.
作为旁注,赋值操作符=将赋值一个新对象,从而销毁引用。下面是一个演示:
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相同的对象。因此,我们现在可以修改orange的color方法而不影响apple的color方法,但是如果我们从apple克隆另一个对象,这个新对象将在复制的实例变量中引用与apple相同的对象。
dup will also produce a shallow copy of the object it is copying, and if you were to do the same demonstration shown above to dup, you will see it works exactly the same way. But there are two major differences between clone and dup. First, as others mentioned, clone copies the frozen state and dup does not. What does this mean? The term 'frozen' in Ruby is an esoteric term for immutable, which itself is a nomenclature in computer science, meaning that something cannot be changed. Thus, a frozen object in Ruby cannot be modified in any way; it is, in effect, immutable. If you attempt to modify a frozen object, Ruby will raise a RuntimeError exception. Since clone copies the frozen state, if you attempt to modify a cloned object, it will raise a RuntimeError exception. Conversely, since dup does not copy the frozen state, no such exception will occur, as we'll demonstrate:
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
如您所见,fruit对象实例的单例类被复制到克隆中。因此,克隆对象可以访问单例方法:seeds ?但这不是dup的情况:
apple = fruit.dup
=> #<Fruit:0x007fdafe0c6558 @origin=:plant>
apple.seeded?
=> NoMethodError: undefined method `seeded?'
Now in prototype-based programming, you do not have classes which extend other classes and then create instances of classes whose methods derive from a parent class that serves as a blueprint. Instead, you have a base object and then you create a new object from the object with its methods and state copied over (of course, since we are doing shallow copies via clone, any objects the instance variables reference will be shared just as in JavaScript prototypes). You can then fill in or change the object's state by filling in the details of the cloned methods. In the below example, we have a base fruit object. All fruit have seeds, so we create a method number_of_seeds. But apples have one seed, and so we create a clone and fill in the details. Now when we clone apple, we not only cloned the methods but we cloned the state! Remember clone does a shallow copy of the state (instance variables). And because of that, when we clone apple to get a red_apple, red_apple will automatically have 1 seed! You can think of red_apple as an object that inherits from Apple, which in turn inherits from Fruit. Hence, that is why I capitalized Fruit and Apple. We did away with the distinction between classes and objects courtesy of clone.
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原型行为的东西。
两者几乎完全相同,但克隆比dup多做一件事。在克隆中,对象的冻结状态也会被复制。在dup里,它总是会融化的。
f = 'Frozen'.freeze
=> "Frozen"
f.frozen?
=> true
f.clone.frozen?
=> true
f.dup.frozen?
=> false
一个不同之处在于冷冻物体。冻结对象的克隆也被冻结(而冻结对象的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!
子类可以覆盖这些方法以提供不同的语义。在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">