Jaka jest różnica między metodami duplikatu i klonowania Ruby?


214

Dokumenty Rubydup mówią:

Ogólnie rzecz biorąc, clonei dupmoże mieć różną semantykę w klasach potomnych. Chociaż clonesłuży do duplikowania obiektu, w tym jego stanu wewnętrznego, dupzwykle używa klasy obiektu potomnego do utworzenia nowej instancji.

Ale kiedy wykonuję jakiś test, okazało się, że są one 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

Jakie są zatem różnice między tymi dwiema metodami?


29
Chciałbym wiedzieć, nie tylko różnicę między tym, co robi dupi coclone robi, ale dlaczego używasz jednego zamiast drugiego.
Andrew Grimm

1
tutaj jest również dobry link - coderwall.com/p/1zflyg
Arup Rakshit

Odpowiedzi:


298

Podklasy mogą zastąpić te metody, aby zapewnić inną semantykę. W Objectsobie, istnieją dwie zasadnicze różnice.

Najpierw clonekopiuje klasę singleton, a dupnie robi tego.

o = Object.new
def o.foo
  42
end

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

Po drugie, clonezachowuje stan zamrożenia, podczas gdy dupnie.

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

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

Realizacja Rubinius dla tych metod jest często moje źródło odpowiedzi na te pytania, ponieważ jest to dość oczywiste, a realizacja dość zgodny Ruby.


15
W przypadku, gdy ktoś spróbuje to zmienić ponownie: „klasa singleton”, która jest dobrze zdefiniowanym terminem w Ruby, obejmuje nie tylko metody singleton , ale także wszelkie stałe zdefiniowane w klasie singleton. Rozważmy: o = Object.new; class << o; A=5; end; puts ( class << o.clone; A; end ); puts ( class << o.dup; A; end ).
Jeremy Roman

3
świetna odpowiedź, po której nastąpił świetny komentarz, ale doprowadziło mnie to do dzikiej gęsi, aby zrozumieć tę składnię. pomoże to wszystkim innym, którzy również mogą być zdezorientowani: devalot.com/articles/2008/09/ruby-singleton
davidpm4

1
Myślę, że warto wspomnieć, że „klasa singleton” obejmuje również wszelkie moduły, które zostały extendedytowane na oryginalnym obiekcie. Object.new.extend(Enumerable).dup.is_a?(Enumerable)Zwraca więc wartość false.
Daniel

Chociaż te odpowiedzi odpowiadają na pytanie i wskazują różnice. Warto również zauważyć, że obie metody są przeznaczone do różnych sytuacji, zgodnie z dokumentacją Object # dup . Przypadkiem użycia dla klonowania jest klonowanie obiektu z zamiarem użycia go jako tej samej instancji (mając inny identyfikator obiektu), podczas gdy dup jest przeznaczony do duplikowania obiektu jako podstawy dla nowej instancji.
3limin4t0r

189

W przypadku ActiveRecord istnieje również znacząca różnica:

dup tworzy nowy obiekt bez ustawionego identyfikatora, dzięki czemu można zapisać nowy obiekt w bazie danych, naciskając .save

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

clone tworzy nowy obiekt o tym samym identyfikatorze, więc wszystkie zmiany dokonane w tym nowym obiekcie zastąpią oryginalny rekord w przypadku uderzenia .save

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

43
Ta odpowiedź jest tą, która ma najważniejsze informacje praktyczne IMO ... inne odpowiedzi dotyczą ezoteryki, podczas gdy ta odpowiedź wskazuje na krytyczną różnicę praktyczną.
jpw

37
Powyższe dotyczy tylko ActiveRecord; rozróżnienie jest znacznie bardziej subtelne w standardowym Ruby.
ahmacleod

1
@Stefan i @jvalanen: Kiedy aplikuję dupi clonemetody na mój ActiveRecordobiekt, otrzymuję odwrotne wyniki tego, o czym wspomniałeś w odpowiedzi. co oznacza, że ​​kiedy używam dup, tworzy nowy obiekt z idustawionym, a podczas używania clonetworzy obiekt bez idustawiania. czy możesz zajrzeć do niego jeszcze raz i wyjaśnić? . Thnx
huzefa biyawarwala

Nic się nie zmieniło w Rails 5 albo: api.rubyonrails.org/classes/ActiveRecord/... . Więc wierzę, że w twoim przypadku jest coś wyjątkowego ...
jvalanen

Jednak clonewprowadzenie nowego rekordu, który nigdy nie został zapisany, powinno być całkiem bezpieczne? Czy mogę w ten sposób zbudować „obiekt szablonu” i sklonować go, aby zapisać określone wystąpienia?
Cyril Duchon-Doris,

30

Jedną różnicą są zamrożone przedmioty. cloneZamrożonej przedmiotu jest również zamrożone (podczas gdy dupzamrożonej obiektu nie).

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

Kolejna różnica dotyczy metod singletonowych. Ta sama historia tutaj, dupnie kopiuje ich, ale clonerobi.

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!

To było dla mnie bardzo przydatne. Jeśli tworzysz zamrożoną stałą wartość i przekazujesz ją do czegoś takiego: github.com/rack/rack/blob/master/lib/rack/utils.rb#L248 (obsługa plików cookie w Railsach), możesz łatwo otrzymać błąd kiedy nie wiedzą o tobie, klonują go, a następnie próbują zmodyfikować klon. podwojenie zamrożonej wartości i przekazanie tego pozwala przynajmniej zagwarantować, że nikt przypadkowo nie zmodyfikuje twojej stałej, bez łamania Rack tutaj.
XP84

4

Oba są prawie identyczne, ale klon robi coś więcej niż dup. W klonie kopiowany jest również stan zamrożenia obiektu. W dupleksie zawsze będzie rozmrażany.

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

4

Nowsze 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>

0

Klonu można użyć do programowania opartego na prototypach w języku Ruby. Klasa Object Ruby definiuje zarówno metodę klonowania, jak i metodę dup. Zarówno klon, jak i dup tworzą płytką kopię obiektu, który kopiuje; to znaczy zmienne instancji obiektu są kopiowane, ale nie obiekty, do których się odnoszą. Pokażę 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ż, że w powyższym przykładzie pomarańczowy klon kopiuje stan (tj. Zmienne instancji) obiektu Apple, ale w przypadku gdy obiekt Apple odwołuje się do innych obiektów (takich jak kolor obiektu String), te odniesienia nie są kopiowane. Zamiast tego jabłko i pomarańcza odnoszą się do tego samego obiektu! W naszym przykładzie referencją jest obiekt łańcuchowy „czerwony”. Gdy pomarańczowy używa metody dołączania <<, aby zmodyfikować istniejący obiekt String, zmienia obiekt string na „czerwony pomarańczowy”. To w efekcie zmienia także apple.color, ponieważ oba wskazują na ten sam obiekt String.

Na marginesie, operator przypisania = = przypisze nowy obiekt i 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 nowy obiekt do metody instancji kolorów pomarańczowego klonu, nie odnosi się on już do tego samego obiektu co jabłko. Dlatego możemy teraz zmodyfikować metodę koloru pomarańczowego bez wpływu na metodę koloru jabłka, ale jeśli sklonujemy inny obiekt z jabłka, nowy obiekt będzie odwoływał się do tych samych obiektów w skopiowanych zmiennych instancji jak jabłko.

dup wytworzy również płytką kopię obiektu, który kopiuje, a jeśli miałbyś zrobić taką samą demonstrację, jak pokazano powyżej, zobaczysz, że działa dokładnie w ten sam sposób. Istnieją jednak dwie główne różnice między klonowaniem a duplikatem. Po pierwsze, jak wspomniano inni, klon kopiuje stan zamrożenia, a duplikat nie. Co to znaczy? Termin „zamrożony” w Rubim jest ezoterycznym terminem niezmiennym, który sam w sobie jest nomenklaturą w informatyce, co oznacza, że ​​czegoś nie można zmienić. Dlatego zamrożonego obiektu w Rubim nie można w żaden sposób modyfikować; w rzeczywistości jest niezmienny. Jeśli spróbujesz zmodyfikować zamrożony obiekt, Ruby zgłosi wyjątek RuntimeError. Ponieważ klon kopiuje stan zamrożenia, próba zmodyfikowania sklonowanego obiektu spowoduje zgłoszenie wyjątku RuntimeError. I odwrotnie, ponieważ dup nie kopiuje stanu zamrożenia,

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

Po drugie i, co ciekawsze, klon kopiuje klasę singleton (a stąd i jej metody)! Jest to bardzo przydatne, jeśli chcesz podjąć się programowania opartego na prototypach w Rubim. Najpierw pokażmy, że rzeczywiście metody singletonowe są kopiowane z klonem, a następnie możemy zastosować je w przykładzie programowania opartego na prototypach w 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 

Jak widać klasa singleton instancji obiektu owocowego jest kopiowana do klonu. I tak sklonowany obiekt ma dostęp do metody singleton: seeded ?. Ale nie jest tak w przypadku dup:

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

Obecnie w programowaniu opartym na prototypach nie ma klas, które rozszerzają inne klasy, a następnie tworzą instancje klas, których metody pochodzą od klasy nadrzędnej, która służy jako plan. Zamiast tego masz obiekt podstawowy, a następnie tworzysz nowy obiekt z obiektu za pomocą jego metod i stanu skopiowano (oczywiście, ponieważ wykonujemy płytkie kopie za pośrednictwem klonowania, wszelkie obiekty, do których odwołują się zmienne instancji, będą udostępniane tak jak w JavaScript prototypy). Następnie możesz wypełnić lub zmienić stan obiektu, wypełniając szczegóły sklonowanych metod. W poniższym przykładzie mamy podstawowy obiekt owocowy. Wszystkie owoce mają nasiona, dlatego tworzymy metodę liczba nasion. Ale jabłka mają jedno ziarno, więc tworzymy klon i uzupełniamy szczegóły. Teraz, kiedy klonujemy jabłko, nie tylko klonujemy metody, ale także klonujemy państwo! Pamiętaj, że klon wykonuje płytką kopię stanu (zmienne instancji). Z tego powodu, gdy sklonujemy jabłko, aby uzyskać red_apple, red_apple automatycznie otrzyma 1 ziarno! Możesz myśleć o red_apple jako obiekcie, który dziedziczy po Apple, który z kolei dziedziczy po Fruit. Dlatego właśnie dokapitalizowałem Fruit and Apple. Zlikwidowaliśmy rozróżnienie między klasami a przedmiotami dzięki uprzejmości klonów.

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 klonowania, możesz uzyskać coś podobnego do zachowania prototypowego JavaScript.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.