0. Duck Typing (API Contract)
Before starting, let's look at something interesting, Duck Typing simply put is "behavior determines type".
Different from languages like Java that have mandatory compile-time type checking, JavaScript is dynamically typed and supports duck typing. For example:
function getDinner(chief) {
chief.cook('DINNER');
}
var littleBoy = {
cook: function() {
// ...
}
}
var dog = {
wang: {
// ...
}
}
var dinner1 = getDinner(littleBoy);
// var dinner2 = getDinner(dog); // Uncaught TypeError: chief.cook is not a function
We defined a getDinner method, parameter requires chief, equivalent to semantically constraining type Chief, the definition of this type is anything with cook method counts. From the test results above we can see: type checking happens at runtime, if type doesn't match it throws Error. Hmm, "behavior determines type" is correct, right?
1. Factory Pattern
function LittleDog() {
// ...
}
function LovelyDog() {
// ...
}
function BeautifulDog() {
// ...
}
function DogFactory() {}
DogFactory.prototype.type = LittleDog; // Default creates LittleDog
DogFactory.prototype.getDog = function(spec) {
spec = spec || {};
if (spec.type === 'LovelyDog') {
this.type = LovelyDog;
}
else if (spec.type === 'BeautifulDog') {
this.type = BeautifulDog;
}
return new this.type(spec);
}
// test
var dogFactory = new DogFactory();
var dog1 = dogFactory.getDog(); // Creates default littledog
var dog2 = dogFactory.getDog({
type: 'LovelyDog',
attr: 'val' // Other initialization data, passed by spec to specific constructor
});
The implementation above is the JavaScript version of factory pattern, puts all factory attributes on prototype object, overrides type on prototype object inside getDog, has a bit of singleton pattern flavor.
2. Advantages and Disadvantages of Factory Pattern
1. Advantages
- Reduces complexity of object creation process
Factory shields the connection between object and current environment, calling factory to create objects doesn't need to consider current environment
- Easy to manage similar small objects or components
When there are only slight differences between objects/components, using factory pattern to directly produce objects is a better choice than inheritance
- Convenient for producing "duck type" objects
For example defined a duck type Chief, can use factory method to produce objects of this type (just add cook method to object in factory method), without needing to define type for the sake of defining type
2. Disadvantages
- Has additional overhead
Factory pattern's essence is secondary encapsulation of a group of related constructors, therefore brings additional overhead (one more layer)
- Not conducive to unit testing
Object creation process is hidden behind factory, if object creation process is very complex, may bring problems to unit testing
3. Abstract Factory Pattern
Can also implement abstract factory pattern with JavaScript, and implementation is simpler than Java, example code is as follows:
var factory = (function() {
// Store vehicle types
var types = {};
return {
getVechicle: function(type, customizations) {
var Vechicle = types[type];
return (Vechicle) ? new Vechicle(customizations) : null;
},
registerVechicle: function(type, Vechicle) {
var proto = Vechicle.prototype;
// Only register classes that implement vehicle contract
if (proto.drive && proto.breakDown) {
types[type] = Vechicle;
}
}
}
})();
Abstract factory provides registerXXX interface to uniformly manage constructor methods, and internally constrains types with "API contract". After registering constructor methods, call getVechicle to create objects, for example:
// Constructor methods (for simplicity, directly return parameter object)
function Car(arg) {
return arg;
}
Car.prototype.drive = function() {};
Car.prototype.breakDown = function() {};
function Truck(arg) {
return arg;
}
Truck.prototype = Car.prototype;
// Usage
factory.registerVechicle('car', Car);
factory.registerVechicle('truck', Truck);
// Instantiate a new car object based on abstract vehicle type
var car = factory.getVechicle('car', {
color: 'lime green',
state: 'like new'
});
// Similarly, instantiate a new truck object
var truck = factory.getVechicle('truck', {
wheelSize: 'medium',
color: 'neon yellow'
});
console.log(car);
console.log(truck);
Compared to the first example of factory pattern, here actually just separated constructor and factory (took if(type === 'xxx') out of factory.create), can flexibly register constructors
Has big difference from classic abstract factory pattern's meaning, at most can only count as an enhancement (optimization) to factory pattern, but that doesn't matter, after all not for pursuing using design patterns to use, as long as can achieve moderate decoupling purpose is good
For more information about abstract factory pattern, please check 2 blog posts cited in reference materials section
Reference Materials
-
"JavaScript Design Patterns"
No comments yet. Be the first to share your thoughts.