Ви можете використовувати клон, щоб робити програмування на основі прототипу в 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.
dup
і щоclone
робить, але чому б ви використовували один, а не інший.