Prototype

Buffer this pageShare on FacebookPrint this pageTweet about this on TwitterShare on Google+Share on LinkedInShare on StumbleUpon
Reading Time: 3 minutes

Prototype Design Pattern in Python

Prototype Design Pattern in Python

Design Patterns Home
 

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

  1. 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.
  2. Prototypical Class: Class denoting the object whose variations will be made. In our example, this is the class Car.
  3. Prototypical object: An object of the Prototypical class that will be supplied to the registerObject() method of the Prototype class with a 'name'.
  4. 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.

Pseduo 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

  1. First, we create our prototypical object i.e. an object of prototypical class Car.
  2. Then, we create an instance of the prototype class.
  3. 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.
  4. 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.

  1. 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.
  2. 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
    
  3. 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.

    Python Documentation

  4. 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]
    
  5. Deep Copy
  6. 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


 

 

 

 


See also:

Buffer this pageShare on FacebookPrint this pageTweet about this on TwitterShare on Google+Share on LinkedInShare on StumbleUpon

Leave a Reply