Design patterns are reusable templates that help us solve software design problems using best practices. In this way, they help us build applications using code that is easier to maintain, understand, reuse and test.
What is this pattern for?
It is a tactic to delay the creation of an object, the calculation of a value, or any other computationally expensive process until the time when it is to be used for the first time.
Let’s go back to the example of the social network we saw in the previous articles of this series. We want to add a feature that shows us the activity of our contacts and those contacts’ contacts (likes, comments, videos, photos, etc). Collecting this information is a rather expensive process in which we have to iterate over our contact list and save the activity of each of them, in addition to that of all their contacts. Therefore, this feature consumes a large amount of resources, both in terms of memory and compute time.
One option would be to build this activity list when starting the app, but that would increase the time the user spends on the loading screen. This is a very bad idea since apps that take more than a few seconds to load are usually uninstalled quickly. Also, the user might not use this feature during their session.
The solution: create the activity list when the user needs it.
How does it work?
The first time we need the expensive resource will be the moment when we will create it. In our case, that resource is the contact activity list and we will save it as a property in the user’s profile. Thus, when the profile is created, the property “contact_list” will be initialized as None (null in other languages).
The method in the Profile class that gives access to that activity list will contain an if statement in which we will check if the list has already been created. If it has already been created because it has been used previously, we will simply return that list. In case the list does not exist, because it will be used for the first time, we will create it and return it.
We can encapsulate the list creation logic in an auxiliary private method, as we see in the following example:
class Profile: def __init__(self): self.contact_activity = None ... def get_contact_activity(self): if not self.contact_activity: self.contact_activity = self._build_contact_activity() return self.contact_activity def _build_contact_activity(self): ...
In the app, when we create the profile, the list will not be generated. If the user does not need it, it will not be created at any time. If, on the other hand, it is needed at some point, the list will be created:
if __name__ == "__main__": profile = Profile() ... # When we need the activity list for the first time. contact_activity = profile.get_contact_activity()
- Speeds up startup time by distributing complex calculations over time.
- Avoids performing operations that are infrequent unless they are necessary.