Jaka jest różnica między metodą dup Rubiego a metodą klonowania?
Ruby docs for dup
powiedz:
Ogólnie rzecz biorąc,
clone
idup
mogą mieć różne semantyki w klasach potomnych. Podczas gdyclone
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?
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.
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">
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!
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>
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
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.
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