Вы можете использовать клон для программирования на прототипах в Ruby. Класс Object в Ruby определяет как метод clone, так и метод dup. И clone, и 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"
Обратите внимание, что в приведенном выше примере оранжевый клон копирует состояние (то есть переменные экземпляра) объекта apple, но там, где объект apple ссылается на другие объекты (например, цвет объекта String), эти ссылки не копируются. Вместо этого яблоко и апельсин оба ссылаются на один и тот же объект! В нашем примере ссылка на строковый объект red. Когда оранжевый использует метод добавления <<, чтобы изменить существующий объект 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'
В приведенном выше примере, когда мы присваиваем новый новый объект методу экземпляра цвета оранжевого клона, он больше не ссылается на тот же объект, что и яблоко. Следовательно, теперь мы можем изменить цветовой метод оранжевого цвета, не затрагивая цветовой метод apple, но если мы клонируем другой объект из apple, этот новый объект будет ссылаться на те же объекты в скопированных переменных экземпляра, что и на apple.
dup также создаст поверхностную копию объекта, который копирует, и если вы выполните ту же демонстрацию, показанную выше, для dup, вы увидите, что она работает точно так же. Но есть два основных различия между клоном и дупом. Во-первых, как уже упоминалось, клон копирует замороженное состояние, а dup - нет. Что это значит? Термин «замороженный» в Ruby - это эзотерический термин для неизменного, который сам по себе является номенклатурой в компьютерной науке, означая, что что-то нельзя изменить. Таким образом, замороженный объект в 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
Как видите, синглтон-класс экземпляра объекта Fruit копируется в клон. И, следовательно, клонированный объект имеет доступ к одноэлементному методу: seeded ?. Но в случае с dup это не так:
apple = fruit.dup
=> #<Fruit:0x007fdafe0c6558 @origin=:plant>
apple.seeded?
=> NoMethodError: undefined method `seeded?'
Теперь в программировании на основе прототипов у вас нет классов, которые расширяют другие классы, а затем создают экземпляры классов, чьи методы являются производными от родительского класса, который служит планом. Вместо этого у вас есть базовый объект, а затем вы создаете новый объект из объекта с копированием его методов и состояния (конечно, поскольку мы делаем мелкие копии с помощью клона, любые объекты, на которые ссылаются переменные экземпляра, будут совместно использоваться, как в JavaScript прототипы). Затем вы можете заполнить или изменить состояние объекта, заполнив детали клонированных методов. В приведенном ниже примере у нас есть базовый фруктовый объект. У всех фруктов есть семена, поэтому мы создаем метод number_of_seeds. Но у яблок есть одно семя, и поэтому мы создаем клон и заполняем детали. Теперь, когда мы клонируем яблоко, мы не только клонировали методы, но и клонировали государство! Помните, что клон делает поверхностную копию состояния (переменные экземпляра). И поэтому, когда мы клонируем яблоко, чтобы получить red_apple, у red_apple автоматически будет 1 семя! Вы можете думать о red_apple как об объекте, который наследуется от Apple, который, в свою очередь, наследуется от Fruit. Следовательно, именно поэтому я использовал заглавные буквы Fruit и Apple. Мы покончили с различием между классами и объектами благодаря клону.
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
делает, но почему бы использовать один, а не другой.