Skip to content

Instantly share code, notes, and snippets.

@patrickkunka
Last active March 6, 2021 21:27
Show Gist options
  • Save patrickkunka/a65a62928f5a9e2acca6d33277d728ce to your computer and use it in GitHub Desktop.
Save patrickkunka/a65a62928f5a9e2acca6d33277d728ce to your computer and use it in GitHub Desktop.
Factory Function Patterns
/**
* Factory #1: Constructor Instantiation
*
* This example shows basic abstraction of constructor/class
* instantation. Additional error checking, validation of arguments
* could be added inside the factory, or just delegated to the constructor.
*/
import Implementation from './Implementation';
export default function factory() {
return new Implementation(...arguments);
}
/**
* Factory #2: Instance Caching (Memoization)
*
* A pattern which is particularly useful for UI widgets, where
* we want to prevent the consumer from creating multiple
* instances on the same element.
*/
import Implementation from './Implementation';
/**
* @param {HTMLElement} input
* @param {object} [config={}]
* @return {Implementation}
*/
function factory(input, config={}) {
let instance = null;
for (let i = 0; (instance = factory.cache[i]); i++;) {
// If instance found in cache with same `input` element, return that instance
// NB: In this example, the implementation exposes a public property holding
// a reference to its input element
if (instance.input === input) return instance;
}
// Else create new instance, add to cache and return it
instance = new Implementation(input, config);
factory.cache.push(instance);
return instance;
}
factory.cache = [];
export default factory;
/**
* Factory #3A: Dynamic Return Types
*
* An example where we might want to allow a factory function
* to return a different type depending on how we interact with
* it.
*
* The factory function accepts either a single element, or an
* array-like collection of elements. It then returns a single
* instance, or an array of instances as appropriate.
*
* While factories give us this flexibility, it should be
* avoided in favour of predictable API entry points. A refactored
* version using a seperate static method is shown afterwards (#3B).
*/
import Implementation from './Implementation';
/**
* @param {(HTMLElement|HTMLElement[])} inputs
* @param {object} [config={}]
* @return {(Implementation|Implementation[])}
*/
export default function factory(inputs, config={}) {
if (Array.isArray(inputs) || typeof inputs.length === 'number') {
// Return many
return Array.from(inputs).map(input => new Implementation(input, config));
}
// Return one
return new Implementation(inputs, config);
}
/**
* Factory #3B: Dynamic Return Types
*
* With this pattern, the most commonly used functionality
* is available via the most simple calling syntax, and more
* specialised functionality is seperated out into static
* methods.
*/
import Implementation from './Implementation';
/**
* @param {HTMLElement} input
* @param {object} [config={}]
* @return {Implementation}
*/
function factory(input, config={}) {
return new Implementation(input, config);
}
/**
* @param {HTMLElement[]} inputs
* @param {object} [config={}]
* @return {Implementation[]}
*/
factory.collection = (inputs, config={}) => {
return Array.from(inputs).map(input => new Implementation(input, config));
}
export default factory;
/**
* Factory #4: Asynchronicity
*
* In this example, both constructor/class instantiation
* and asyncronous functionality is abstracted behind a single
* promise-returning factory. The promise resolves with
* an instance of the implementation.
*/
import Implementation from './Implementation';
/**
* @param {object} [config={}]
* @return {Promise<Implementation>}
*/
export default function factory(config={}) {
const instance = new Implementation(config);
return instance.somethingAsync()
.then(() => instance);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment