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.
dup
i coclone
robi, ale dlaczego używasz jednego zamiast drugiego.