DEV Community

Favour George
Favour George

Posted on • Originally published at psycode.hashnode.dev on

Python's Prototype Design Pattern: Crafting Objects for Performance

In a previous article, we discussed the singleton design pattern and saw how it could be applied in practice. In this article, well be discussing the prototype design pattern, when to use it and how to use it.

The prototype design pattern lets you as a developer copy existing objects without making your code dependent on the classes in which they were instantiated from. So what does this mean? Say you had an object in your code and for some reason you wanted to create an exact copy of that object with its attributes, normally you would first create an object of the same class and then go through all the properties of the original object and copy over their values to the new object. However, it is important to note that not all objects can be copied that way because some of the objects properties (i.e. attributes and methods) may be private. Furthermore, you would have to know the objects class to create a duplicate and in a long codebase, this could become very tedious and this is where the prototype design pattern comes in.

Fundamentally, the prototype pattern is just a clone() function that accepts an object as an argument and returns a clone of the object.

Building a Prototype Design

Let us implement the prototype design in code as follows::

from abc import ABCMeta, abstractmethod
from copy import deepcopy

# prototype interface
class Prototype(metaclass=ABCMeta):
    @abstractmethod
    def clone(self):
        pass

# concrete prototype
class Car(Prototype):
    def __init__ (self, make, model, year) -> None:
        self.make = make
        self.model = model
        self.year = year

    def clone(self):
        return deepcopy(self)

# client
car_1 = Car('Dodge', 'Challenger SRT', 2021)
cloned_car = car_1.clone()

print(car_1)
print(cloned_car)
print(car_1 == cloned_car)

Enter fullscreen mode Exit fullscreen mode

In the code above, we first import the necessary tools well need. We imported the ABCMeta class and abstractmethod decorator from the abc module. The ABCMeta class is used to define an abstract base class and the abstractmethod decorator is used to declare abstract methods that must be implemented by subclasses. We also imported the deepcopy function from the copy module. This function creates a deep copy of an object

Abstract Class

This is a class that cannot be instantiated directly and is meant to be subclassed by other classes.

Abstract Method

This is a method declared in abstract base classes but does not contain any implementation in the base class itself.

We then created a class Prototype , that serves as the interface for creating prototype objects. The metaclass=ABCMeta argument specifies that this class is an abstract class (i.e. a class that cannot be instantiated directly and is meant to be subclassed by other classes) and should be treated as such.

The @abstractmethod decorator marks the clone method as an abstract method. Abstract methods are declared in abstract base classes but do not contain any implementation in the base class itself. Subclasses inheriting from this base class are required to provide an implementation for this method

We then created another class, Car , which implements our Prototype base class. We created a constructor method that is to be initialized when objects are created from the class. We then defined the implementation of our abstract method clone , derived from the Prototype class implemented in our car class. The clone function takes the object as an argument and returns a deep copy of the object.

To test our code, we created a car object from the Car class, specifying all the needed attributes (make, model, year) and we named this object car_1. We then created another object, cloned_car , which is a clone of our first car, thanks to applying the clone method to our car_1 object.

Using our print statements, we can see they are not stored in the same memory address and they are not the same although one is a clone of the other as seen in the output below:

< __main__.Car object at 0x000000FF942AE2E0>
< __main__.Car object at 0x000000FF94327040>
False

Enter fullscreen mode Exit fullscreen mode

Applications of the Prototype Design

The prototype design is useful for:

  • Reducing Object Creation Overload: If object creation is a computationally expensive task, using the prototype design pattern can be more efficient than creating objects from scratch.

  • Creating Stateful Objects: Prototypes can be used to capture the state of an object at a certain point in time, making it useful for tasks such as creating snapshots and the redo/undo functionality.

  • Dynamically Creating Objects: In cases where the exact class of an object isnt known until runtime, the prototype pattern can be helpful to achieve runtime object creation without knowing the specific classes in advance

With all that said, the keypoint is to understand that the prototype design pattern basically creates a clone of an existing object.

Thats it for this article folks, dont just read it but also try to understand and perhaps even use it. So till next time, as usual, dont break production.

Top comments (0)