Apa perbedaan antara metode dup dan klon Ruby?

214

The Ruby docs untukdup mengatakan:

Secara umum, clonedan dupmungkin memiliki semantik yang berbeda di kelas keturunan. Sementara clonedigunakan untuk menduplikasi objek, termasuk keadaan internalnya, dupbiasanya menggunakan kelas objek turunan untuk membuat instance baru.

Tetapi ketika saya melakukan beberapa tes saya menemukan mereka sebenarnya sama:

class Test
   attr_accessor :x
end

x = Test.new
x.x = 7
y = x.dup
z = x.clone
y.x => 7
z.x => 7

Jadi apa perbedaan antara kedua metode tersebut?

cali-1337500
sumber
29
Saya berharap saya tahu bukan hanya perbedaan dalam apa dup dan apa yangclone dilakukan, tetapi mengapa Anda akan menggunakan yang satu dan bukan yang lain.
Andrew Grimm
1
di sini juga ada tautan yang bagus - coderwall.com/p/1zflyg
Arup Rakshit

Jawaban:

298

Subclass dapat mengganti metode ini untuk memberikan semantik yang berbeda. Dalam Objectdirinya sendiri, ada dua perbedaan utama.

Pertama, clonemenyalin kelas singleton, sementara duptidak.

o = Object.new
def o.foo
  42
end

o.dup.foo   # raises NoMethodError
o.clone.foo # returns 42

Kedua, clonemempertahankan kondisi beku, sementara duptidak.

class Foo
  attr_accessor :bar
end
o = Foo.new
o.freeze

o.dup.bar = 10   # succeeds
o.clone.bar = 10 # raises RuntimeError

The pelaksanaan Rubinius untuk metode ini sering sumber saya untuk jawaban atas pertanyaan ini, karena cukup jelas, dan implementasi Ruby yang cukup compliant.

Jeremy Roman
sumber
15
Jika ada yang mencoba mengubah ini lagi: "kelas singleton", yang merupakan istilah yang didefinisikan dengan baik di Ruby, tidak hanya mencakup metode singleton , tetapi juga konstanta yang didefinisikan pada kelas singleton. Pertimbangkan: o = Object.new; class << o; A=5; end; puts ( class << o.clone; A; end ); puts ( class << o.dup; A; end ).
Jeremy Roman
3
jawaban yang bagus, diikuti oleh komentar yang bagus, tetapi itu membuat saya mengejar angsa liar untuk memahami sintaksis itu. ini akan membantu orang lain di luar sana yang mungkin juga bingung: devalot.com/articles/2008/09/ruby-singleton
davidpm4
1
Saya pikir perlu disebutkan bahwa "kelas tunggal" juga mencakup modul apa pun yang telah extenddiedit pada objek asli. Jadi Object.new.extend(Enumerable).dup.is_a?(Enumerable)mengembalikan salah.
Daniel
Meskipun jawaban ini menjawab pertanyaan dan menyatakan perbedaannya. Perlu juga dicatat bahwa kedua metode dimaksudkan untuk situasi yang berbeda sebagaimana dinyatakan oleh dokumentasi Object # dup . Use case untuk clone adalah mengkloning suatu objek dengan maksud untuk menggunakannya sebagai instance yang sama (sementara memiliki id objek yang berbeda), sedangkan dup dimaksudkan untuk menduplikasi objek sebagai basis untuk instance baru.
3limin4t0r
189

Ketika berhadapan dengan ActiveRecord ada perbedaan yang signifikan juga:

dup membuat objek baru tanpa id yang ditetapkan, sehingga Anda dapat menyimpan objek baru ke database dengan menekan .save

category2 = category.dup
#=> #<Category id: nil, name: "Favorites"> 

clone membuat objek baru dengan id yang sama, jadi semua perubahan yang dilakukan pada objek baru itu akan menimpa catatan asli jika memukul .save

category2 = category.clone
#=> #<Category id: 1, name: "Favorites">
jvalanen
sumber
43
Jawaban ini adalah yang memiliki IMO info praktis yang paling penting ... jawaban yang lain berkutat pada esoterika, sedangkan jawaban ini menunjukkan perbedaan praktis yang kritis.
jpw
37
Di atas adalah khusus untuk ActiveRecord; perbedaannya jauh lebih halus dalam Ruby standar.
ahmacleod
1
@Stefan dan @jvalanen: Ketika saya menerapkan dupdan clonemetode pada ActiveRecordobjek saya , saya mendapatkan hasil terbalik dari apa yang Anda sebutkan dalam jawaban. yang berarti ketika saya menggunakan dup, itu menciptakan objek baru dengan itu idsedang diatur dan saat menggunakannya clonemenciptakan objek tanpa itu idsedang diatur. dapatkah kamu memeriksanya kembali dan menjelaskan? . Thnx
huzefa biyawarwala
Tidak ada yang berubah di Rails 5: api.rubyonrails.org/classes/ActiveRecord/… . Jadi saya percaya ada sesuatu yang istimewa dalam kasus Anda ...
jvalanen
Namun, cloneapakah catatan baru yang belum pernah disimpan seharusnya cukup aman? Bisakah saya membuat "objek templat" dengan cara ini, dan mengkloningnya untuk menyimpan instance tertentu?
Cyril Duchon-Doris
30

Salah satu perbedaannya dengan benda beku. The clonebenda beku juga beku (sedangkan dupdari objek beku tidak).

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

Perbedaan lainnya adalah dengan metode tunggal. Kisah yang sama di sini, duptidak menyalinnya, tetapi clonejuga.

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!
Jonathan Fretheim
sumber
Ini sangat berguna bagi saya. Jika Anda membuat nilai konstanta beku dan meneruskannya ke sesuatu seperti ini: github.com/rack/rack/blob/master/lib/rack/utils.rb#L248 (Penanganan cookie rel) maka Anda dapat dengan mudah mendapatkan kesalahan ketika mereka tanpa sepengetahuan Anda, mereka mengkloning dan kemudian mencoba untuk memodifikasi klon. duping nilai beku Anda dan meneruskannya memungkinkan Anda untuk setidaknya menjamin bahwa tidak ada yang secara tidak sengaja memodifikasi konstanta Anda, tanpa melanggar Rack di sini.
XP84
4

Keduanya hampir identik tetapi klon melakukan satu hal lebih dari dup. Dalam klon, keadaan beku objek juga disalin. Dalam dup, itu akan selalu dicairkan.

 f = 'Frozen'.freeze
  => "Frozen"
 f.frozen?
  => true 
 f.clone.frozen?
  => true
 f.dup.frozen?
  => false 
veeresh yh
sumber
4

The doc yang lebih baru termasuk contoh yang baik:

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>
Xavier Nayrac
sumber
0

Anda dapat menggunakan klon untuk melakukan pemrograman berbasis prototipe di Ruby. Kelas Obyek Ruby mendefinisikan metode klon dan metode dup. Baik klon dan dup menghasilkan salinan dangkal objek yang disalin; artinya, variabel instan objek disalin tetapi bukan objek yang dirujuk. Saya akan menunjukkan contoh:

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"

Perhatikan pada contoh di atas, klon oranye menyalin keadaan (yaitu, variabel instance) dari objek apel, tetapi di mana objek apel referensi objek lain (seperti warna objek String), referensi tersebut tidak disalin. Sebaliknya, apel dan oranye sama-sama merujuk objek yang sama! Dalam contoh kita, referensi adalah objek string 'merah'. Ketika oranye menggunakan metode append, <<, untuk memodifikasi objek String yang ada, itu mengubah objek string menjadi 'oranye merah'. Efek ini juga mengubah apple.color, karena keduanya menunjuk ke objek String yang sama.

Sebagai catatan tambahan, operator penugasan, =, akan menetapkan objek baru dan karenanya menghancurkan referensi. Ini sebuah demonstrasi:

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'

Dalam contoh di atas, ketika kami menetapkan objek baru ke metode instance warna klon oranye, itu tidak lagi merujuk objek yang sama seperti apel. Oleh karena itu, kita sekarang dapat memodifikasi metode warna oranye tanpa mempengaruhi metode warna apel, tetapi jika kita mengkloning objek lain dari apel, objek baru itu akan mereferensikan objek yang sama dalam variabel instance yang disalin seperti apel.

dup juga akan menghasilkan salinan dangkal dari objek yang disalin, dan jika Anda melakukan demonstrasi yang sama seperti ditunjukkan di atas untuk dup, Anda akan melihatnya bekerja dengan cara yang persis sama. Tetapi ada dua perbedaan utama antara clone dan dup. Pertama, seperti yang disebutkan orang lain, klon menyalin keadaan beku dan dup tidak. Apa artinya ini? Istilah 'beku' di Ruby adalah istilah esoterik untuk kekal, yang dengan sendirinya merupakan nomenklatur dalam ilmu komputer, yang berarti bahwa sesuatu tidak dapat diubah. Dengan demikian, objek beku di Ruby tidak dapat dimodifikasi dengan cara apa pun; itu, pada dasarnya, tidak berubah. Jika Anda mencoba mengubah objek yang dibekukan, Ruby akan memunculkan pengecualian RuntimeError. Karena klon menyalin keadaan beku, jika Anda mencoba untuk memodifikasi objek yang dikloning, itu akan meningkatkan pengecualian RuntimeError. Sebaliknya, karena dup tidak menyalin keadaan beku,

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

Kedua, dan, yang lebih menarik, mengkloning kelas singleton (dan karenanya metodenya)! Ini sangat berguna jika Anda ingin melakukan pemrograman berbasis prototipe di Ruby. Pertama, mari kita tunjukkan bahwa memang metode tunggal disalin dengan klon, dan kemudian kita bisa menerapkannya dalam contoh pemrograman berbasis prototipe di 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 

Seperti yang Anda lihat, kelas tunggal instance objek buah disalin ke klon. Dan karenanya objek yang dikloning memiliki akses ke metode singleton: seeded ?. Tapi ini tidak terjadi dengan dup:

apple = fruit.dup
 => #<Fruit:0x007fdafe0c6558 @origin=:plant> 
apple.seeded?
=> NoMethodError: undefined method `seeded?'

Sekarang dalam pemrograman berbasis prototipe, Anda tidak memiliki kelas yang memperluas kelas lain dan kemudian membuat contoh kelas yang metodenya berasal dari kelas induk yang berfungsi sebagai cetak biru. Alih-alih, Anda memiliki objek dasar dan kemudian Anda membuat objek baru dari objek tersebut dengan metode dan statusnya disalin (tentu saja, karena kami sedang melakukan salinan dangkal melalui klon, objek apa pun yang referensi variabel instan akan dibagi seperti pada JavaScript prototipe). Anda kemudian dapat mengisi atau mengubah status objek dengan mengisi rincian metode yang dikloning. Dalam contoh di bawah ini, kami memiliki objek buah dasar. Semua buah memiliki biji, jadi kami membuat metode number_of_seeds. Tapi apel punya satu biji, jadi kami membuat klon dan mengisi detailnya. Sekarang ketika kita mengkloning apel, kita tidak hanya mengkloning metode tetapi kita mengkloning negara! Ingat klon melakukan salinan negara yang dangkal (variabel instan). Dan karena itu, ketika kita mengkloning apel untuk mendapatkan red_apple, red_apple akan secara otomatis memiliki 1 seed! Anda dapat menganggap red_apple sebagai objek yang mewarisi dari Apple, yang pada gilirannya mewarisi dari Buah. Karenanya, itulah sebabnya saya menggunakan huruf besar untuk Fruit and Apple. Kami menyingkirkan perbedaan antara kelas dan objek milik klon.

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 

Tentu saja, kita dapat memiliki metode konstruktor dalam pemrograman berbasis 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 

Pada akhirnya, menggunakan klon, Anda bisa mendapatkan sesuatu yang mirip dengan perilaku prototipe JavaScript.

Donato
sumber