When creating features, we often need to create instances based on parameters coming from an endpoint or a similar dynamic source. In this article I'll try to give you an idea of how we can use the single factory pattern to approach such cases.
We'll refactor a piece of code to make it more scalable and understandable. Let's suppose we have this premise:
Given an object of element's data, write an algorithm that allows users to provide a type of element to render it dynamically. Also, please validate the elements as required:
Generic input text fields such as Email and Password should be validated.
Make sure line breaks are stripped off textarea elements.
Example of a source of data, let's call it elementsData.js:
Now I'll write what could be a pseudo "solution" to dynamically create the types of form elements and validate them (note that I'll only define the methods that matter for the purpose of this article):
import config from'./elementsData';exportdefaultclassFormElement{constructor(type){this.type = type;this.elements = config.elements;}getElementByType(){returnthis.type inthis.elements ?this.elements[this.type]:null;}/* This would validate our email*/emailValidator(){...}/* this would remove line breaks from our textareas */textareaSanitizer(){...}/* We would use this to bind all the validators and sanitizers events */bindEventListeners(){...}renderFormElement(){const element =this.getElementByType();if(!element){returnfalse;}switch(this.type){case'email':return`
<div class="field-wrapper">
<input type="email" name=${element.name} placeholder=${element.text} />
</div>
`;break;case:'textarea':return`
<div class="field-wrapper">
<textarea name=${element.name} placeholder=${element.text} />
</div>
`;case'select':return`
<div class="field-wrapper">
<select name=${element.name}>
${element.options.map(option=>`
<option value=${option.value}>
${option.display}
</option>
`)}
</select>
</div>
`;}}}
and we would instantiate the class in our main.js like:
This should work, right? We're consuming the data, dynamically creating elements and validating them... BUT, there are some things we're not seeing. I want you to think: in the future, what would happen with this class when you or someone else needs to add more and more form elements? The renderFormElements method will grow, and we'll end up having a huge method with endless conditions and validation methods - and let's not even talk about the complexity and scalability.
Implementing Single Factory
The factory pattern is a design pattern that's part of the creational group. It basically deals with the issue of creating objects when the class that instantiates it needs to be dynamic. It also helps a lot with organizing your code, because:
Isolates the objects that need to be created.
Promotes small classes with less responsibilities.
Delegates the responsibility of object creation to a class called "factory."
Creates the instances by receiving the dynamic value in your entry point.
Here is a visual representation I created to demonstrate how the factory works:
Now we'll start by refactoring our "solution" based on the list we've created above.
Isolate the objects to keep single responsibilities
The form elements select, email, and textarea can be easily isolated by moving the logic that involves them to a folder called /FormElements or /FormElementTypes (you can give any name that makes sense):
Notice that we're moving the validation and binding methods to the element's class, and we would do the same for the other elements (textarea, select, etc.). This will allow us to scale and keep the logic for each type of element isolated.
Delegate the responsibility of object creation to a class called "factory"
The factory does a couple of things:
Imports the types of elements.
Creates an ELEMENTS object with the types of elements available.
Creates a static method to be able to create instances directly by using the class name.
Dynamically instantiates based on the type passed.
Creates the instances by receiving the dynamic value in your entry point (our main.js, in this case).
Nothing complex to explain here, we're just validating that the argument passed exists in our object of elements, if it does then we create an instance by using the factory method createInstance, we pass down the data needed and render the element with the render method. You can see the code below:
main.js
import ElementsFactory as Factory from'./factories/FormElementsFactory';import config from'./elementsData';functionInit(){/* This could be received dynamically */const dynamicElement ='email';if(!(dynamicElement in config.elements)){returnfalse;}const element = Factory.createInstance(config.elements[dynamicElement]);
element.render();}Init();
To finish, here is a representation of our folder structure after refactoring:
Cool, right? Now every time you want to add a new element, it's just a matter of adding it to the /formElements folder and importing it in our factory so it can be instantiated, Also, if you want to remove an element, it's just a matter of deleting the import line and the file from the /formElements folder.
Ok, I think that's it for this article guys, and I hope you were able to understand a little more about the factory pattern. If you did, remember to share it on Twitter or Facebook. You can also subscribe to our email list below.
Big O notation allows us to evaluate the performance of algorithms so that we can determine their efficiency and make decisions based on this determinations, let's try to understand how this notation works and how we can apply it in our lives as software developers.Read more
Linked Lists are a fundamental data structure in the world of software development, in this article we will explore its implementation and its applications in today's worldRead more