Have you ever imported an object from a library and tried to clone it, only to fail because cloning it requires extensive knowledge of the library’s internals?
Or perhaps, after working on a project for a long time, you took a break to refactor your code and noticed that you’re recloning many complex objects in various parts of your codebase?
Well, the prototype design pattern has got you covered!
In this article, we will explore the prototype design pattern while building a fully functional journaling templates Node.js CLI application.
Without further ado, let’s dive into it!
Overview
Prototype is a creational design pattern, which is a category of design patterns that deals with the different problems that come with the native way of creating objects with the new keyword or operator.
Problem
The factory design pattern solves the following creational problems:
-
How can you copy an existing object in your application without depending on its concret classes?
-
Some complex objects are hard to clone, because they either have a lot of fields which need a particular busines logic that is either not know by you or or has a lot of private fields which are not accessible from outside of the objects.
Let's take as example the socket object imported from the socket.io library, imaging having to clone that yourself?
You will have to go through it's code inside the library, understand how sockets work, the objects has even some circular dependencies which you have to deal with your self in order to clone it.
In addition to that, Your code will depend on the socket class or interface and the corresponding business logic to create it, which violates the solid dependency inversion principle and makes your code less robust for changes.
Solution
The prototype design pattern solves these problems, by delegating the responsiblitiy of copying the object into the object itself, by declaring a clone method in every object's class which is meant to be clonable.
Structure
To implement the prototype design pattern you can either directly include the clone method inside the clonnable object.
Or create a common interface Prototype which can be implemented by all of the clonnable objects.
One benifit of having a common interface is the ability to register all the prototypes, in a common registery service class, which will be responsible on cahing the frequently used prototypes and return them to the user. Instead of having to clone the objects everytime the clone method gets called.
That can be really handy especially when cloning complex objects.
Practical Scenario
In this section, we're going to demo this design pattern by building a mini journaling templates Nodejs CLI application.
As we saw earlier the prototype design pattern delegates the responsiblity of cloning the object into the object itself.
But have you wondered why it's even called prototype?I mean what has that to do with cloning?
We will be answering that through this practical example, keep reading and stay tuned.
You can find the final code in this repository. Just clone it and run the following commands.
Creating our Prototype: Journaling Template Class
First let's create a JournalTemplate which has the following attributes:
- name: We will need it to identity the template.
- sections: A section is a portion of the journaling template which is reserved to a specific theme or topic such as: Gratitude, Challenges, Tomorrow's Goals....
Each section is consist of the following attributes:
- title The topic or theme of the section: Gratitude, Challenges, Tomorrow's Goals...
- prompt The message which will be displayed to the user when he is about to write the section journaling text.
JournalTemplate.ts
The JournalTemplate class has many utility methods for setting its diffirent attributes.
The display method will be used later to display a colored well formated output to the terminal.
the chalk package is used to color some pieced for the outputed terminal text.
Our JournalTemplate objects are meant to be used as the name implies as templates or prototypes for creating other templates or journaling file entries.
That's why we've added the clone method to the JournalTemplate class.
We've added it to give the responsibility of handling the cloning business logic to the JournalTemplate object itself rather than the consuming code.
Declaring Journaling Template Registery
Now let's create our TemplateRegistry class, which will be responsible on storing the JournalTemplate class prototype instances. While providing methods for manipulating those instances.
TemplateRegistry.ts
The registery stores those classes in a Map object, for fast retreival by name, and exposes many utility methods for adding or retireiving templates instances.
Instantiating The Journaling Template Registery
Now, Let's instantiate the template registery and then seed some initial templates.
registry.ts
Defining the templates actions methods
In this section, we will define a bunch of functions which will be used in our application menu, to execute various actions like:
- Prompt the user to enter the name of the template, then recurrsively prompt it again to create as many sections as he want.
- View All the existing or created templates.
- Use a template to create a journaling file entry.
- Create a new template, from an existing template: The user will be asked to select an existing template then he will have the ability to either use it directly or override its name and sections.
The newly created templates can be used to create new journaling entries (1).
Create a Template:
TemplateActions.ts > createTemplate
- To create a template we first prompt the user to enter a template name.
- Then we instantiate a new template object, with the name and an empty array for the sections.
- After that, we prompt the user to enter the details of the sections, after entering every section's informations, the user can choose to either stop or enter more sections.
utils.ts > promptForSectionDetails
The promptForSectionDetails function use the inquirer package to ask for the title, then prompt sequentially from the user.
View The templates:
TemplateActions.ts > viewTemplates
The viewTemplates function works as follow:
- We first get all the templates from the registry, then we loop through the returned templates array and use the display method which we've defined earlier in the JournalTemplate class.
Use a template to create a Journaling Entry: The reason for creating journaling templates, is to make our lives easier when writing our different types of journals, instead of facing an empty page, it's better easier to fill the journal when faced with a bunch of sequential sections titles and prompts.
Let's dive into the useTemplate function:
- First we select one template among the existing templates, after getting the template names from the registery.
- For every section in the template, the user will asked to open his prefered editor to fill the journal section text.
TemplateActions.ts > useTemplate
Create a Template From an Existing Template:
Finally, We are going to see the prototype design pattern in action.
Let's explore how can we create new types of templates dynamicly by overriding the existing templates.
- First we prompt the user to select the template that he wants to override from the existing ones.
- Then we prompt it again to type the name of the newly created template.
- We use the registry to get the template given the template name which is selected by the user.
- We use the clone method to get a clone object that matches the selected template.
As you can see from the code bellow, we don't even need to know about the details of the JournalTemplate class or to polute our code by importing it.
TemplateActions.ts > createFromExistingTemplate
- Finally, we set the template name given by the user to the newly created object, and then prompt the user to perform any crud operations on the existing template sections using the editTemplateSections method, which we will be explaining bellow just after the code block.
templateSectionsAction > editTemplateSections
The editTemplateSections defined bellow basically prompts displays a menu, asking the user to override the existing sections as needed by offering different operations like:
- Add Section
- Remove Section
- Edit Section
Application menu
Finally, We make use of all of the previous functions in our index.ts file, which bootsraps the cli app, and displays a menu with the different template manipulation options:
- Create a Template.
- Create a Template from an Existing Template.
- View Templates.
- Use a Template to create a journaling entry.
- Exit the program.
index.ts
Conclusion
The Prototype design pattern provides a powerful way to create new objects by cloning existing ones. In our journaling template application, we've seen how this pattern allows us to create new templates based on existing ones, demonstrating the flexibility and efficiency of the Prototype pattern.
By using this pattern, we've created a system that's easy to extend and modify, showcasing the true power of object-oriented design patterns in real-world applications.
Contact
If you have any questions or want to discuss something further feel free to Contact me here.
Happy coding!