Яка різниця між методами дублінгу та клонування Ruby?


214

Документи 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

То які відмінності між двома методами?


29
Мені б хотілося, щоб я знала не просто різницю в тому, що dup і що cloneробить, але чому б ви використовували один, а не інший.
Ендрю Грімм

1
ось хороше посилання також - coderwall.com/p/1zflyg
Arup Rakshit

Відповіді:


298

Підкласи можуть замінити ці методи, щоб забезпечити різну семантику. В 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.


15
У випадку, якщо хтось спробує змінити це ще раз: "клас однотонних", що є чітко визначеним терміном в Ruby, включає не тільки методи однотонних , але і будь-які константи, визначені для класу синглтон. Розглянемо: o = Object.new; class << o; A=5; end; puts ( class << o.clone; A; end ); puts ( class << o.dup; A; end ).
Джеремі Роман

3
чудова відповідь, за якою пішов чудовий коментар, але це змусило мене в погоні за дикими гусками зрозуміти цей синтаксис. це допоможе іншим там, хто також може заплутатися: devalot.com/articles/2008/09/ruby-singleton
davidpm4

1
Я думаю, що варто згадати, що "однокласний клас" включає також будь-які модулі, які були extendвідредаговані на оригінальному об'єкті. Так Object.new.extend(Enumerable).dup.is_a?(Enumerable)повертається помилковим.
Даніель

Хоча ці відповіді відповідають на питання та констатують відмінності. Варто також зазначити, що обидва способи призначені для різних ситуацій, як зазначено в документації щодо дублювання об'єкта # . Випадок використання для клонування - це клонування об’єкта з наміром використовувати його як той самий екземпляр (маючи при цьому інший ідентифікатор об'єкта), тоді як dup призначений для копіювання об'єкта як бази для нового екземпляра.
3limin4t0r

189

У роботі з ActiveRecord також є суттєва різниця:

dup створює новий об’єкт без встановлення його ідентифікатора, тож ви можете зберегти новий об’єкт у базі даних, натиснувши його .save

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

clone створює новий об'єкт з тим самим ідентифікатором, тому всі зміни, внесені до цього нового об'єкта, замінять оригінальний запис при натисканні .save

category2 = category.clone
#=> #<Category id: 1, name: "Favorites">

43
ЦИЙ відповідь - це той, у якому IMO є найважливішою практичною інформацією ... інші відповіді стосуються езотерики, тоді як ця відповідь визначає критичну практичну різницю.
jpw

37
Наведене вище характерне для ActiveRecord; відмінність набагато тонкіша у стандартного Ruby.
ахмаклеод

1
@Stefan та @jvalanen: Коли я застосовую dupі cloneметоди на своєму ActiveRecordоб'єкті, я отримую зворотні результати того, що ви згадали у відповіді. що означає, що коли я використовую dup, він створює новий об'єкт з його idвстановленням, а під час cloneйого використання створює об'єкт без його idвстановлення. ви можете, будь ласка, заглянути в нього ще раз і очистити? . Thnx
huzefa biyawarwala

У Rails 5 також нічого не змінилося: api.rubyonrails.org/classes/ActiveRecord/… . Тож я вважаю, що у вашому випадку є щось особливе ...
jvalanen

Однак cloneнова запис, який ніколи не зберігався, повинен бути тоді досить безпечним? Чи можна таким чином створити "об’єкт шаблону" та клонувати його для збереження конкретних примірників?
Кирило Духон-Доріс

30

Одна різниця - із замороженими предметами. 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!

Це мені було дуже корисно. Якщо ви створюєте заморожене постійне значення і передаєте його на щось подібне: github.com/rack/rack/blob/master/lib/rack/utils.rb#L248 (обробка файлів cookie Rails), ви можете легко отримати помилку коли вони вам невідомі, вони клонують його, а потім намагаються модифікувати клон. дублювання замороженого значення та передача цього дозволяє вам принаймні гарантувати, що ніхто випадково не змінює вашу константу, не порушуючи тут Rack.
XP84

4

Обидва майже однакові, але клон робить ще одне, ніж дуп. У клон заморожений стан об'єкта також копіюється. У дупі він завжди буде танути.

 f = 'Frozen'.freeze
  => "Frozen"
 f.frozen?
  => true 
 f.clone.frozen?
  => true
 f.dup.frozen?
  => false 

4

Новий документ включає в себе хороший приклад:

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>

0

Ви можете використовувати клон, щоб робити програмування на основі прототипу в Ruby. Клас Oby Ruby визначає як метод клонування, так і метод 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"

Зауважте у наведеному вище прикладі, помаранчевий клон копіює стан (тобто змінні екземпляра) яблучного об'єкта, але там, де яблучний об'єкт посилається на інші об'єкти (наприклад, колір об'єкта String), ці посилання не копіюються. Натомість яблуко та апельсин посилаються на один і той же об’єкт! У нашому прикладі посилання є об'єктом рядка "червоний". Коли помаранчевий використовує метод додавання <<, щоб змінити існуючий об'єкт String, він змінює об'єкт рядка на "червоний оранжевий". Це фактично змінює також 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 також створить дрібну копію об'єкта, який він копіює, і якщо ви повинні виконати ту ж демонстрацію, як показано вище, для копіювання, ви побачите, що вона працює точно так само. Але є дві основні відмінності між клоном і дупом. По-перше, як згадували інші, клон копіює заморожений стан, а дуб - не. Що це означає? Термін «заморожений» у Рубі - це езотеричний термін для незмінного, який сам по собі є номенклатурою інформатики, що означає, що щось неможливо змінити. Таким чином, заморожений об'єкт у 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. Спочатку покажемо, що дійсно однотонні методи копіюються з клоном, а потім ми можемо застосувати його на прикладі програмування на основі прототипу в 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. Але яблука мають одне насіння, і тому ми створюємо клон і заповнюємо деталі. Тепер, коли ми клонували яблуко, ми не тільки клонували методи, але й клонували державу! Пам'ятайте, клон робить дрібну копію стану (змінні екземпляра). І тому, коли ми клонуємо яблуко, щоб отримати червоний яблуко, червоний яблуко автоматично матиме 1 насіння! Ви можете думати red_apple як об’єкт, який успадковує від Apple, який, у свою чергу, успадковує від Fruit. Отже, саме тому я з великої величини використав Фрукти та Яблуко. Ми усунули відмінність між класами та об'єктами люб’язно клонірованими.

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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.