Share This Article
The prototype pattern in Ruby is a creational design pattern used when the type of objects to create is determined by a prototypical instance, which is cloned to produce new things.
This pattern is used to:
- Avoid subclasses of an object creator
- Avoid the inherent cost of creating a new object in the standard way (e.g., using the ‘new’ keyword)
- It is used when the creation of a new thing is inefficient
Let’s explore a simple example:
class Graphic
attr_accessor :label
def initialize(label)
@label = label
end
def draw
puts "Drawing #{@label}"
end
def clone
self.class.new(@label)
end
end
class Rectangle < Graphic
def initialize(label)
super
end
def draw
super
puts "It's a rectangle"
end
end
rec1 = Rectangle.new("First rectangle")
rec2 = rec1.clone
rec1.draw
rec2.draw
# Prints:
# Drawing First rectangle
# It's a rectangle
# Drawing First rectangle
# It's a rectangle
As you can see, the clone
method on the Rectangle
the class creates a new instance by calling the new
method on itself.
It is essential that the clone
method is defined on the superclass (Graphic
in this case). This way, all subclasses (e.g., Rectangle
) will inherit the clone
method.
Cloning with Marshal
In the example above, we’ve implemented the clone
method manually.
But what if we want to clone an object we don’t have control over (e.g., a Ruby class that we didn’t write)?
For that, we can use the Marshal
module.
require "marshal"
class Person
attr_accessor :first_name, :last_name
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
end
p1 = Person.new("John", "Doe")
p2 = Marshal.load(Marshal.dump(p1))
puts p2.inspect
#
Shallow vs Deep Cloning
The example above clones an object completely (deep cloning).
But sometimes, you may only want to clone some attributes (shallow cloning).
In that case, you need to implement the clone
method yourself.
You can do that by creating a new instance of the class and then copying the values of the attributes you want to clone.
Here’s a simple example:
class Cloneable
attr_accessor :id, :name, :job
def initialize(id, name, job)
@id = id
@name = name
@job = job
end
def clone
self.class.new(@id, @name, @job)
end
def inspect
"#{id} - #{name} - #{job}"
end
end
cln = Cloneable.new(1, "John", "Developer")
cln2 = cln.clone
puts cln.inspect
puts cln2.inspect
# Prints:
# 1 - John - Developer
# 1 - John - Developer
As you can see, the clone
method above creates a new instance of the Cloneable
class with the same id
, name
and job
attributes.
But what about the other attributes?
In this case, it doesn’t copy those attributes because they don’t exist yet.
This is commonly referred to as a ‘shallow copy’ because it only copies the top-level attributes of an object.
Performance of Cloning
To clone many objects, you must know the performance implications. The performance of using the clone
method will depend on your object’s size. The larger the object, the longer it will take to clone it.
When to Use the Prototype Pattern
The prototype pattern is a good choice when:
- The classes to instantiate are specified at runtime, for example, by dynamic loading; or
- A class can have one or more different outcomes when instantiated (e.g. MusicFile can be an MP3 or WAV, etc.).
The prototype pattern is also suitable for objects that are very heavy to instantiate.
For example, if you want to instantiate an object that requires a lot of resources or a lot of memory, it might be better to use this pattern.
Summary
In this article, you’ve learned about the prototype pattern. This pattern is used when the type of object to create is determined by a prototypical instance cloned to produce new objects.
The prototype pattern is a good choice when the classes to instantiate are specified at runtime or when you want to avoid creating many similar objects.