The Factory pattern is another creational pattern concerned with the notion of creating objects. Where it differs from the other patterns in its category is that it doesnât explicitly require the use of a constructor. Instead, a Factory can provide a generic interface for creating objects, where we can specify the type of factory object we wish to be created (Figure 9-9).
Imagine that we have a UI factory where we are asked to create a
type of UI component. Rather than creating this component directly using
the new
operator or via another
creational constructor, we ask a Factory object for a new component
instead. We inform the Factory what
type of object is required (e.g., âButtonâ, âPanelâ) and it instantiates
this, returning it to us for use.
This is particularly useful if the object creation process is relatively complexâe.g., if it strongly depends on dynamic factors or application configuration.
Examples of this pattern can be found in UI libraries such as ExtJS, where the methods for creating objects or components may be further subclassed.
The following is an example that builds upon our previous snippets
using the Constructor pattern logic to define cars. It demonstrates how a
vehicle
factory may be implemented using the Factory
pattern:
// Types.js - Constructors used behind the scenes
// A constructor for defining new cars
function
Car
(
options
)
{
// some defaults
this
.
doors
=
options
.
doors
||
4
;
this
.
state
=
options
.
state
||
"brand new"
;
this
.
color
=
options
.
color
||
"silver"
;
}
// A constructor for defining new trucks
function
Truck
(
options
){
this
.
state
=
options
.
state
||
"used"
;
this
.
wheelSize
=
options
.
wheelSize
||
"large"
;
this
.
color
=
options
.
color
||
"blue"
;
}
// FactoryExample.js
// Define a skeleton vehicle factory
function
VehicleFactory
()
{}
// Define the prototypes and utilities for this factory
// Our default vehicleClass is Car
VehicleFactory
.
prototype
.
vehicleClass
=
Car
;
// Our Factory method for creating new Vehicle instances
VehicleFactory
.
prototype
.
createVehicle
=
function
(
options
)
{
if
(
options
.
vehicleType
===
"car"
){
this
.
vehicleClass
=
Car
;
}
else
{
this
.
vehicleClass
=
Truck
;
}
return
new
this
.
vehicleClass
(
options
);
};
// Create an instance of our factory that makes cars
var
carFactory
=
new
VehicleFactory
();
var
car
=
carFactory
.
createVehicle
(
{
vehicleType
:
"car"
,
color
:
"yellow"
,
doors
:
6
}
);
// Test to confirm our car was created using the vehicleClass/prototype Car
// Outputs: true
console
.
log
(
car
instanceof
Car
);
// Outputs: Car object of color "yellow", doors: 6 in a "brand new" state
console
.
log
(
car
);
In Approach 1, we modify a VehicleFactory
instance to use the Truck
class:
var
movingTruck
=
carFactory
.
createVehicle
(
{
vehicleType
:
"truck"
,
state
:
"like new"
,
color
:
"red"
,
wheelSize
:
"small"
}
);
// Test to confirm our truck was created with the vehicleClass/prototype Truck
// Outputs: true
console
.
log
(
movingTruck
instanceof
Truck
);
// Outputs: Truck object of color "red", a "like new" state
// and a "small" wheelSize
console
.
log
(
movingTruck
);
In Approach 2, we subclass VehicleFactory
to
create a factory class that builds Trucks
:
function
TruckFactory
()
{}
TruckFactory
.
prototype
=
new
VehicleFactory
();
TruckFactory
.
prototype
.
vehicleClass
=
Truck
;
var
truckFactory
=
new
TruckFactory
();
var
myBigTruck
=
truckFactory
.
createVehicle
(
{
state
:
"omg..so bad."
,
color
:
"pink"
,
wheelSize
:
"so big"
}
);
// Confirms that myBigTruck was created with the prototype Truck
// Outputs: true
console
.
log
(
myBigTruck
instanceof
Truck
);
// Outputs: Truck object with the color "pink", wheelSize "so big"
// and state "omg. so bad"
console
.
log
(
myBigTruck
);
The Factory pattern can be especially useful when applied to the following situations:
When our object or component setup involves a high level of complexity.
When we need to easily generate different instances of objects depending on the environment we are in.
When weâre working with many small objects or components that share the same properties.
When composing objects with instances of other objects that need only satisfy an API contract (a.k.a., duck typing) to work. This is useful for decoupling.
When applied to the wrong type of problem, this pattern can introduce an unnecessarily great deal of complexity to an application. Unless providing an interface for object creation is a design goal for the library or framework we are writing, I would suggest sticking to explicit constructors to avoid the unnecessary overhead.
Due to the fact that the process of object creation is effectively abstracted behind an interface, this can also introduce problems with unit testing depending on just how complex this process might be.
It is also useful to be aware of the Abstract Factory pattern, which aims to encapsulate a group of individual factories with a common goal. It separates the details of implementation of a set of objects from their general usage.
An Abstract Factory pattern should be used where a system must be independent from the way the objects it creates are generated or it needs to work with multiple types of objects.
An example that is both simple and easier to understand is a
vehicle factory, which defines ways to get or register vehicles types.
The abstract factory can be named
AbstractVehicleFactory
. The abstract factory will
allow the definition of types of vehicle like car
or
truck
, and concrete factories will implement only
classes that fulfill the vehicle contract (e.g., Vehicle.prototype.drive
and Vehicle.prototype.breakDown
).
var
AbstractVehicleFactory
=
(
function
()
{
// Storage for our vehicle types
var
types
=
{};
return
{
getVehicle
:
function
(
type
,
customizations
)
{
var
Vehicle
=
types
[
type
];
return
(
Vehicle
?
new
Vehicle
(
customizations
)
:
null
);
},
registerVehicle
:
function
(
type
,
Vehicle
)
{
var
proto
=
Vehicle
.
prototype
;
// only register classes that fulfill the vehicle contract
if
(
proto
.
drive
&&
proto
.
breakDown
)
{
types
[
type
]
=
Vehicle
;
}
return
AbstractVehicleFactory
;
}
};
})();
// Usage:
AbstractVehicleFactory
.
registerVehicle
(
"car"
,
Car
);
AbstractVehicleFactory
.
registerVehicle
(
"truck"
,
Truck
);
// Instantiate a new car based on the abstract vehicle type
var
car
=
AbstractVehicleFactory
.
getVehicle
(
"car"
,
{
color
:
"lime green"
,
state
:
"like new"
}
);
// Instantiate a new truck in a similar manner
var
truck
=
AbstractVehicleFactory
.
getVehicle
(
"truck"
,
{
wheelSize
:
"medium"
,
color
:
"neon yellow"
}
);
Get Learning JavaScript Design Patterns now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.