The Factory Method Pattern, also known as the Factory Pattern, Virtual Constructor Pattern, or Polymorphic Factory Pattern, belongs to the category of class creation patterns. In the Factory Method Pattern, the factory parent class is responsible for defining the public interface for creating product objects, while the factory subclass is responsible for generating specific product objects. The purpose of this approach is to defer the instantiation of the product class to the factory subclass, allowing the determination of which specific product class to instantiate through the factory subclass.
The Simple Factory Pattern has a drawback where the creation of classes depends on the factory class. In other words, to extend the program, modifications to the factory class are necessary. The Factory Method Pattern delegates the specific creation process to dedicated factory subclasses, thereby delaying the instantiation of the product class to the factory subclass. Consequently, there is no need to modify the factory class, only the definition of the subclass implementing the factory is required.
The Factory Method Pattern is an abstract and extended version of the Simple Factory Pattern. By utilizing the polymorphism of object-oriented programming, the Factory Method Pattern preserves the advantages of the Simple Factory Pattern while overcoming its limitations. In the Factory Method Pattern, the core factory class no longer assumes responsibility for creating all products, but delegates the specific creation work to its subclasses. The core class is only accountable for providing the interface that concrete factories must implement, and does not concern itself with the details of instantiating a specific product class, allowing the introduction of new products without modifying the factory role.
In the Factory Method Pattern, the factory method is used to create the products needed by the client, while concealing the details of which specific product class will be instantiated, thus allowing the user to focus solely on the factory corresponding to the required product, without concerning themselves with creation details or even needing to know the class name of the specific product.
The key to the Factory Method Pattern lies in the polymorphic design of the factory role and product role. It allows the factory to autonomously determine which product object to create, while completely encapsulating the specifics of creating the object within the concrete factory. Another advantage of using the Factory Method Pattern is its extensibility; introducing a new product to the system does not require modifications to the abstract factory, abstract product interfaces, the client, or any other concrete factories or products. Only the addition of a specific factory and product is necessary. This enhances the system's extensibility, fully compliant with the "open-closed principle."
When adding new products, new concrete product classes need to be written, along with corresponding concrete factory classes, leading to an increase in the number of classes in the system, thereby increasing the system's complexity, as more classes require compilation and execution, resulting in additional overhead for the system.
Due to the consideration of system extensibility, an abstract layer needs to be introduced, leading to the use of abstract layers in client code definitions, thereby increasing the system's abstraction and complexity, potentially requiring the use of reflection and other techniques, ultimately increasing the system's implementation complexity.
The original intention of the Factory Method Pattern is to delegate the actual object creation work to the subclass, converting the core class into an abstract class. However, in JavaScript, it is challenging to implement abstract classes as in traditional object-oriented programming. Therefore, in JavaScript, it suffices to refer to its core concept.
classShape{// Base product classsay(){ console.log(this.name);}}classRectangleextendsShape{// Rectangle productconstructor(){super();this.name ="Rectangle";}}classSquareextendsShape{// Square productconstructor(){super();this.name ="Square";}}classCircleextendsShape{// Circle productconstructor(){super();this.name ="Circle";}}classFactory{// Base factory classgetProduct(){}}classRectangleFactoryextendsFactory{constructor(){super();}getProduct(){returnnewRectangle();}}classSquareFactoryextendsFactory{constructor(){super();}getProduct(){returnnewSquare();}}classCircleFactoryextendsFactory{constructor(){super();}getProduct(){returnnewCircle();}}var rectangle =(newRectangleFactory).getProduct();rectangle.say();// Rectangle
var square =(newSquareFactory).getProduct();square.say();// Squarevar circle =(newCircleFactory).getProduct();circle.say();// Circle
// Example of assembling shapes // If you need to assemble three shapesclassShape{// Base class for shapessay(){ console.log(this.name);}}classRectangleextendsShape{// Rectangleconstructor(){super();this.name ="Rectangle";}}classSquareextendsShape{// Squareconstructor(){super();this.name ="Square";}}classCircleextendsShape{// Circleconstructor(){super();this.name ="Circle";}}classCombination{constructor(rectangle, square, circle){ console.log(`Combination: ${rectangle.name}${square.name}${circle.name}`);}}// Direct assembly: the caller only needs a complete combination of shapes, but to assemble them, three shapes need to be instantiated separately, resulting in high coupling.(function(){var rectangle =newRectangle();var square =newSquare();var circle =newCircle();newCombination(rectangle, square, circle);})();// Using a factory: hides specific implementation details and reduces couplingclassCombinationFactory{getProduct(){var rectangle =newRectangle();var square =newSquare();var circle =newCircle();returnnewCombination(rectangle, square, circle);}}(function(){var combination =(newCombinationFactory).getProduct();})();