Welcome back! So far, you've learned about design patterns and how they ensure structured and maintainable code. Now, we're moving on to an essential creational design pattern: the Factory Method Pattern. This pattern is all about creating objects in a more flexible way than direct instantiation. You'll learn how to implement your own factory methods to instantiate different types of objects and see how this pattern allows your code to handle new object types with ease.
The Factory Method Pattern is a creational design pattern that provides an interface for creating an object but allows subclasses to alter the type of objects that will be created. This pattern promotes loose coupling by eliminating the need to specify the exact class of the object that will be created. Instead, the instantiation is handled by subclasses.
You should consider using the Factory Method Pattern when object creation requires conditional logic, when working with large class hierarchies, or when developing frameworks and libraries that need to allow users to extend and customize object creation.
To understand how the Factory Method Pattern is implemented, let's break the process down into intermediate steps.
In JavaScript, you can mimic abstract behavior using ES6 class syntax. JavaScript doesn't have native abstract classes like other languages, but you can enforce abstract methods by throwing errors if a derived class doesn't implement the required methods.
JavaScript1class Document { 2 open() { 3 throw new Error("Method 'open()' must be implemented."); 4 } 5}
Next, create concrete subclasses of Document
. Each subclass will implement the open
method. For example, let's define WordDocument
and ExcelDocument
.
JavaScript1class WordDocument extends Document { 2 open() { 3 console.log("Opening Word document."); 4 } 5} 6 7class ExcelDocument extends Document { 8 open() { 9 console.log("Opening Excel document."); 10 } 11}
In JavaScript, define a creator class using inheritance to provide an interface for creating documents.
JavaScript1class DocumentCreator { 2 createDocument() { 3 throw new Error("Method 'createDocument()' must be implemented."); 4 } 5}
Create concrete subclasses of DocumentCreator
. Each subclass will implement the createDocument
method to instantiate and return a specific type of document.
JavaScript1class WordDocumentCreator extends DocumentCreator { 2 createDocument() { 3 return new WordDocument(); 4 } 5} 6 7class ExcelDocumentCreator extends DocumentCreator { 8 createDocument() { 9 return new ExcelDocument(); 10 } 11}
Finally, use the factory methods to create document objects without specifying their concrete classes.
JavaScript1const creator1 = new WordDocumentCreator(); 2let doc1 = creator1.createDocument(); 3doc1.open(); 4// Output: Opening Word document. 5 6const creator2 = new ExcelDocumentCreator(); 7let doc2 = creator2.createDocument(); 8doc2.open(); 9// Output: Opening Excel document.
Here's the complete implementation of the Factory Method Pattern based on the steps we've discussed using JavaScript:
JavaScript1class Document { 2 open() { 3 throw new Error("Method 'open()' must be implemented."); 4 } 5} 6 7class WordDocument extends Document { 8 open() { 9 console.log("Opening Word document."); 10 } 11} 12 13class ExcelDocument extends Document { 14 open() { 15 console.log("Opening Excel document."); 16 } 17} 18 19class DocumentCreator { 20 createDocument() { 21 throw new Error("Method 'createDocument()' must be implemented."); 22 } 23} 24 25class WordDocumentCreator extends DocumentCreator { 26 createDocument() { 27 return new WordDocument(); 28 } 29} 30 31class ExcelDocumentCreator extends DocumentCreator { 32 createDocument() { 33 return new ExcelDocument(); 34 } 35} 36 37// Usage 38const creator1 = new WordDocumentCreator(); 39let doc1 = creator1.createDocument(); 40doc1.open(); 41// Output: Opening Word document. 42 43const creator2 = new ExcelDocumentCreator(); 44let doc2 = creator2.createDocument(); 45doc2.open(); 46// Output: Opening Excel document.
One of the key advantages of the Factory Method Pattern is its flexibility and extensibility. For instance, adding a new type of document, such as a PdfDocument
, does not require changes to the existing DocumentCreator
or Document
classes. Instead, you can create a new subclass of Document
and a corresponding subclass of DocumentCreator
.
JavaScript1class PdfDocument extends Document { 2 open() { 3 console.log("Opening PDF document."); 4 } 5} 6 7class PdfDocumentCreator extends DocumentCreator { 8 createDocument() { 9 return new PdfDocument(); 10 } 11}
This extensibility makes the pattern ideal for applications where new types of objects are frequently added. You can introduce new products without altering the existing structure, enhancing the maintainability and evolvability of your codebase.
The Factory Method Pattern is essential because it promotes flexibility and scalability in your code designs. By delegating the creation of objects to factory methods, you can easily introduce new types of objects without changing the existing code, leading to better maintainability. Whether you're developing software libraries, frameworks, or complex applications, this pattern helps you manage and scale object creation efficiently. Ready to enhance your coding skills? Let's delve into the Factory Method Pattern and see how you can apply it to write cleaner, more maintainable code. Let's start!