Prototype Design Pattern in Python
What is it?
The Prototype Design Pattern involves creating a basic object, called the prototype. This prototype has default values for its attributes. Then, in order to make customized objects, this prototype is cloned, with altered values for its attributes. Take example of a Car. The prototypical Car object has, say, attributes: make='Honda', color='Blue', enginePower='2000cc'. This prototype can be used to create different cars of variable makes, colors and engine powers. The pattern provides a convenient way of configuring basic attributes of the prototype to make individual objects.
It is classified under Creational Design Patterns, since it provides a widely accepted method of instantiating classes.
Why the need for it: Problem Statement
There is no real "need" for the Prototype Pattern per se, but the Prototype Pattern does provide an organized method of creating customizable objects. The prototype serves as a foundation for each object, allowing the user to give it a unique identity by tweaking the attributes copied from prototype. The Prototype Pattern enables the user to create several identical objects, which is an expensive process otherwise.
Terminology
- Prototype Class: A special class 'Prototype', having methods to register/unregister to-be-cloned objects and clone these objects. These are briefly explained below:
- __init__: The __init__ method creates a dictionary object which stores to-be-cloned objects.
- registerObject: The register method registers the object to be cloned. In our example, this is an object of the class Car. The register method takes two arguments, 'name' & 'obj'. These values denote the key-value pair which will be entered in the dictionary that contains the to-be-cloned objects. In our example, we register a Car object and give it a name of 'basicCar'.
- unregisterObject: Deletes the mentioned to-be-cloned object from the dictionary.
- clone: The clone method clones/replicates the prototypical object. It provides a way of updating the basic attributes of the basic object. It returns the cloned object.
- Prototypical Class: Class denoting the object whose variations will be made. In our example, this is the class Car.
- Prototypical object: An object of the Prototypical class that will be supplied to the registerObject() method of the Prototype class with a 'name'.
- Cloned object: A variation of the primary product, having its own identity. It is obtained by calling the clone() method of the prototype with the above 'name' as its argument.
Pseudo Code
class Car: '''Prototypical class''' def __init__(): '''Gives each object three attributes and initializes them to default values''' def __str__(): '''Returns the string representation of the object when we print the object.''' class Prototype: '''Prototype class''' def __init__(): '''Creates a dictionary object which stores to-be-cloned objects''' def registerObject(name, obj): '''Registers the object to be cloned. It takes two arguments, 'name' & 'obj'. These values denote the key-value pair which will be entered in the dictionary that contains the to-be-cloned objects.''' def unregisterObject(name): ''''Deletes the mentioned to-be-cloned object from the dictionary.''' def clone(name, **kwargs): '''Clones/Replicates the prototypical object. It provides a way of updating the basic attributes of the basic object. The __dict__ represents all the attributes of the object i.e. engine, color & seats. Returns the cloned object.''' # USING THE SETUP create our prototypical object i.e. an object of prototypical class Car create an instance of the prototype class, say 'defaultCar' register the to-be-cloned object i.e defaultCar; to do this, call the registerObject() method, provide a name to it, say, 'basicCar', and the second argument is the prototypical object i.e. 'defaultCar' to clone the object 'basicCar', call the clone method and assign it to a new variable; optionally, we can provide keyword arguments if we wish to change the basic attributes. use the cloned object to suit your needs
How to implement it
import copy class Car: '''Prototypical class''' def __init__(self): ''''Gives each object three attributes and initializes them to default values''' self.engine = "3200cc" self.color = "Blue" self.seats = "5" def __str__(self): '''Returns the string representation of the object when we print the object.''' return '{} | {} | {}'. format(self.engine, self.color, self.seats) class Prototype: '''Prototype class''' def __init__(self): '''Creates a dictionary object which stores to-be-cloned objects''' self._toBeClonedObjects = {} def registerObject(self, name, obj): '''Registers the object to be cloned. It takes two arguments, 'name' & 'obj'. These values denote the key-value pair which will be entered in the dictionary that contains the to-be-cloned objects.''' self._toBeClonedObjects[name] = obj def unregisterObject(self, name): '''Deletes the mentioned to-be-cloned object from the dictionary.''' del self._toBeClonedObjects[name] def clone(self, name, **kwargs): '''Clones/Replicates the prototypical object. Deepcopy is used for cloning, since it creates new compound object with fresh copies of attributes found in the original. The clone method provides a way of updating the basic attributes of the basic object. The __dict__ represents all the attributes of the object i.e. engine, color & seats. Returns the cloned object.''' clonedObject = copy.deepcopy(self._toBeClonedObjects.get(name)) clonedObject.__dict__.update(kwargs) return clonedObject defaultCar = Car() # Prototypical object: this is the object that will be cloned. prototype = Prototype() prototype.registerObject('basicCar', defaultCar) # registering the defaultCar in toBeCloned dictionary with its key as 'basicCar' carOne = prototype.clone('basicCar') # Cloned object print("Details of carOne:", carOne) # OUTPUT: Details of carOne: 3200cc | Blue | 5 carTwo = prototype.clone('basicCar', color = "Black") # another Cloned object print("Details of carTwo:", carTwo) # OUTPUT: Details of carTwo: 3200cc | Black | 5
Walkthrough of implementation
- First, we create our prototypical object i.e. an object of prototypical class Car.
- Then, we create an instance of the prototype class.
- Then, we register the to-be-cloned object i.e defatulCar. To do this, we call the registerObject() method, provide a name to it, 'basicCar', and the second argument is the prototypical object i.e. defaultCar.
- To clone the object 'basicCar', we call the clone method and assign it to a new variable. Optionally, we can provide keyword arguments if we wish to change the basic attributes.
A word on deep copy
There are three ways to copy objects in Python: assignment, shallow copy & deep copy.
- Normal assignment points the new variable towards the original object. Let's use the builtin id() function prove this. The id() function returns the object's memory address.
objOne = [1, 2, 3] objTwo = [4, 5, 6] objThree = [objOne, objTwo] objFour = objThree print( id(objThree) == id(objFour) ) # True as objThree & objFour are the same object print( id(objThree[0]) == id(objFour[0]) ) # True as objThree[0] & objFour[0] are the same object
- Shallow copy
The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or class instances):
A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.
A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.import copy objFour = copy.copy(objThree) print( id(objThree) == id(objFour) ) # False as objFour is a new object print( id(objThree[0]) == id(objFour[0]) ) # True as objFour[0] is the same object as objThree[0]
- Deep Copy
objFour = copy.deepcopy(objThree) print( id(objThree) == id(objFour) ) # False as objFour is a new object print( id(objThree[0]) == id(objFour[0]) ) # False as objFour[0] is a new object
Related to: Abstract Factory
- Creational Patterns
- Factory
- Abstract Factory
- Prototype
- Singleton
- Builder
- Architectural Pattern
- Model View Controller (MVC)