Jaka jest różnica między metodą dup Rubiego a metodą klonowania?

Ruby docs for dup powiedz:

Ogólnie rzecz biorąc, clone i dup mogą mieć różne semantyki w klasach potomnych. Podczas gdy clone jest używany do powielania obiektu, w tym jego wewnętrznego stanu, dup zazwyczaj używa klasy potomnego obiektu do tworzenia nowej instancji.

Ale kiedy robię jakiś test stwierdziłem, że są one rzeczywiście takie same:

class Test
   attr_accessor :x
end

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

Więc jakie są różnice między tymi dwoma metodami?

 221
Author: Andrew Marshall, 2012-04-17

6 answers

Podklasy mogą nadpisywać te metody, aby zapewnić różne semantyki. W samym Object istnieją dwie zasadnicze różnice.

Po pierwsze, clone kopiuje klasę singleton, podczas gdy dup nie.

o = Object.new
def o.foo
  42
end

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

Po Drugie, clone zachowuje stan zamrożenia, podczas gdy dup nie.

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

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

Implementacja Rubiniusa dla tych metod jest często moim źródłem odpowiedzi na te pytania, ponieważ jest dość jasne i dość zgodne z implementacją Ruby.

 306
Author: Jeremy Roman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-30 22:58:07

W przypadku ActiveRecord jest też znacząca różnica:

dup tworzy nowy obiekt bez ustawionego identyfikatora, więc możesz zapisać nowy obiekt w bazie danych, naciskając .save

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

clone tworzy nowy obiekt o tym samym id, więc wszystkie zmiany wprowadzone w tym nowym obiekcie nadpiszą oryginalny rekord, jeśli naciśniesz .save

category2 = category.clone
#=> #<Category id: 1, name: "Favorites">
 194
Author: jvalanen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-21 07:47:34

Jedna różnica polega na zamrożonych przedmiotach. clone zamrożonego obiektu jest również zamrożony (podczas gdy dup zamrożonego obiektu nie jest).

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
Inną różnicą jest metoda Singletona. Ta sama historia tutaj, dup nie kopiuje ich, ale clone robi.
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!
 30
Author: Jonathan Fretheim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-17 00:30:45

Nowszy doc zawiera dobry przykład:

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>
 5
Author: Xavier Nayrac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-15 12:06:35

Oba są prawie identyczne, ale clone robi coś więcej niż dup. W klonie kopiowany jest również zamrożony stan obiektu. W dup zawsze się rozmraża.

 f = 'Frozen'.freeze
  => "Frozen"
 f.frozen?
  => true 
 f.clone.frozen?
  => true
 f.dup.frozen?
  => false 
 4
Author: veeresh yh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-05 13:42:01

Możesz użyć clone do programowania opartego na prototypach w Rubim. Klasa obiektu Ruby definiuje zarówno metodę clone, jak i metodę dup. Zarówno clone, jak i dup tworzą płytką kopię kopiowanego obiektu; to znaczy, że zmienne instancji obiektu są kopiowane, ale nie obiekty, do których się odwołują. Zademonstruję przykład:

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"

Zauważ w powyższym przykładzie, klon orange kopiuje stan (czyli zmienne instancji) obiektu apple, ale gdzie obiekt apple odwołuje się do innych obiektów( np. String object color), odwołania te nie są kopiowane. Zamiast tego apple i orange odwołują się do tego samego obiektu! W naszym przykładzie referencją jest obiekt string 'red'. Kiedy orange używa metody append

Na marginesie, operator przypisania,=, przypisze nowy obiekt, a tym samym zniszczyć odniesienie. Oto demonstracja:

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'

W powyższym przykładzie, kiedy przypisaliśmy nowy obiekt do metody wystąpienia koloru klonu orange, nie odwołuje się on już do tego samego obiektu co apple. W związku z tym możemy teraz zmodyfikować metodę kolorów orange bez wpływu na metodę kolorów apple, ale jeśli sklonujemy inny obiekt z apple, ten nowy obiekt będzie odwoływał się do tych samych obiektów w skopiowanych zmiennych instancji, co apple.

Dup będzie również wyprodukuj płytką kopię obiektu, który kopiuje, a jeśli wykonasz tę samą demonstrację pokazaną powyżej, zobaczysz, że działa dokładnie w ten sam sposób. Ale są dwie główne różnice między clone i dup. Po pierwsze, jak inni wspomnieli, klon kopiuje stan zamrożony, a dup nie. Co to znaczy? Termin "zamrożony" w Ruby jest ezoterycznym terminem niezmiennym, który sam w sobie jest nomenklaturą w informatyce, co oznacza, że czegoś nie można zmienić. Tak więc zamrożony obiekt w Ruby nie może być w żaden sposób modyfikowany; w efekcie jest niezmienny. Jeśli spróbujesz zmodyfikować zamrożony obiekt, Ruby wywoła wyjątek RuntimeError. Ponieważ klonowanie kopiuje zamrożony stan, jeśli spróbujesz zmodyfikować sklonowany obiekt, wywoła to wyjątek RuntimeError. Z drugiej strony, ponieważ dup nie kopiuje stanu zamrożonego, taki wyjątek nie wystąpi, co zademonstrujemy:

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

Drugi, a co ciekawsze, klon kopiuje klasę singleton (i stąd jej metody)! To jest bardzo przydatne, jeśli chcesz podjąć się programowania opartego na prototypach w Ruby. Najpierw pokażmy, że rzeczywiście metody Singletona są kopiowane za pomocą clone, a następnie możemy zastosować je w przykładzie programowania opartego na prototypach w Rubim.

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 

Jak widać, Klasa singleton instancji obiektu fruit jest kopiowana do klonu. I stąd sklonowany obiekt ma dostęp do metody singleton :seeded?. Ale tak nie jest w przypadku dup:

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

Teraz w programowaniu opartym na prototypach, nie mają klas, które rozszerzają inne klasy, a następnie tworzą instancje klas, których metody wywodzą się z klasy nadrzędnej, która służy jako schemat. Zamiast tego masz obiekt bazowy, a następnie tworzysz nowy obiekt z obiektu z jego metodami i stanem skopiowanymi (oczywiście, ponieważ wykonujemy płytkie kopie przez klon, wszelkie obiekty, od których odwołuje się zmienna instancji, będą współdzielone tak jak w prototypach JavaScript). Następnie można wypełnić lub zmienić stan obiektu, wypełniając szczegóły dotyczące sklonowanych metod. W poniższym przykładzie mamy podstawowy obiekt owocowy. Wszystkie owoce mają nasiona, więc tworzymy metodę number_of_seeds. Ale jabłka mają jedno nasiono, więc tworzymy klon i uzupełniamy szczegóły. Teraz, kiedy klonujemy apple, nie tylko sklonowaliśmy metody, ale sklonowaliśmy stan! Remember clone wykonuje płytką kopię stanu (zmiennych instancji). I z tego powodu, kiedy klonujemy apple, aby uzyskać red_apple, red_apple automatycznie będzie miał 1 ziarno! You can think of red_apple jako obiekt, który dziedziczy po Apple, który z kolei dziedziczy po Fruit. Dlatego właśnie pisałem wielką literą owoce i jabłka. Usunęliśmy rozróżnienie między klasami i przedmiotami dzięki uprzejmości 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 

Oczywiście, możemy mieć metodę konstruktora w programowaniu opartym na 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 

Ostatecznie, używając clone, można uzyskać coś podobnego do zachowania prototypu JavaScript.

 1
Author: Donato,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-02-06 17:26:50