Factory functions provide a lightweight pattern for object creation in JavaScript. Unlike classes, factory functions offer flexibility through customizable construction, private data encapsulation, and dynamic prototypes. With UI complexity increasing in modern applications, understanding factory functions is key for scalable and modular JavaScript code.

What Makes Factories Powerful?

Some key strengths of the factory pattern:

Simplicity

Factories avoid class boilerplate and new calls by using normal functions and syntax:

function createUser(name) {
  return {
    name: name
  }; 
}

Encapsulation

Closures give factories private data not exposed in returned objects:

function createCounter() {
  let count = 0;

  return {
    getCount() {
      return count;
    }
  }
} 

Dynamic Prototypes

Object.create() allows factories to inherit from any arbitrary object:

const proto = {
  logThis() {
    console.log(this);
  }
};

function createUser(name) {
  let user = Object.create(proto);
  user.name = name;

  return user;
}

Containment

Factories allow grouping objects without needing class hierarchies.

Efficiency

Factory instances require fewer resources than equivalent class constructs. [1]

These capabilities make factories ideal for creating compartmentalized and disposable objects.

Performance and Memory Characteristics

Factories can provide better performance in some cases due to avoiding class abstractions. Some JSPerf benchmarks show:

  • 2-4x less memory usage for factories creating objects with single properties [2]
  • Faster instantiation through normal functions instead of new calls [3]

However, accessor heavy objects perform 2-8x slower with factories due to identifier resolution differences. [1]

So factories have tradeoffs depending on object access patterns. They work best for creating many disposable objects.

Common Real-World Use Cases

Some areas where factory functions shine:

UI Components

For UI elements like popups, notifications and widgets, factories provide flexibility:

function createPopup(html, duration) {

  return {
    render(rootElem) {
      // mount popup DOM to rootElem
    },
    destroy() {
      // teardown popup 
    }
  };

}

const modal = createPopup(‘<div/>‘, 1000); 

Creates reusable popups without needing classes.

Game Objects

Dynamic attribute assignment allows factories to model unique game characters/assets:

function createCharacter(name, powers) {

  let char = { 
    name: name
  };

  // assign special powers
  powers.forEach(p => char[p] = function() {
    // power ability
  });

  return char;
}

const mage = createCharacter(‘Merlin‘, [‘fireball‘, ‘teleport‘]); 

Easy to expand capabilities.

Math Utilities

Factories provide namespacing without boxy hierarchies:

const vec2 = {

  create(x, y) {
    let vec = Object.create(vec2);  
    vec.x = x;
    vec.y = y;

    return vec;
  },

  magnitude(vec) {
    return Math.sqrt(vec.x*vec.x + vec.y*vec.y);
  }

};

const point = vec2.create(3, 4);
vec2.magnitude(point); // 5

Keeps related functions together.

These examples demonstrate factory flexibility for domain needs without rigid taxonomy.

Industry Trends

More developers are leveraging factories as web apps grow in complexity:

  • React uses factories for creating component instances [4]
  • Vue employs factories to extend component options [5]
  • Angular utilizes factories for services and controllers [6]

Many libraries also use factories to enable customization like Redux [7].

These trends align with increased UI dynamism requiring object versatility that factories provide.

Managing Object Relations

A downside of factories is organizing domain objects without hierarchies. Some patterns that help:

Factory Constructor Hybrid

Use factories to encapsulate constructor logic:

function Person(name) {
  this.name = name;
}

function createPerson(name, age) {
  let person = new Person(name);
  person.age = age;

  return person;
}

Keeps Person constructor reusable while exposingfactory API.

Factory Roles

Define factory roles for consistent construction:

const factory = {

  base(traits) {
    // common object setup 
  },

  admin(traits) { 
    // admin extensions
  } 

};

factory.base({name: ‘Sarah‘});
factory.admin({name: ‘Jane‘}); 

Provides base object abstraction.

Extendable Factories

Support plugins/extensions:

function createGame(settings) {
  let game = {
    // base game setup
  };

  function installPlugin(plugin) {
    // attach plugin modules 
  }

  return Object.assign(game, { installPlugin });  
}

const game = createGame({...});
game.installPlugin(graphicsModule);

Allows expanding capabilities.

These patterns demonstrate approaches to organize domain logic across factories.

Architectural Tradeoffs

Classes provide more formal structure with inheritance, whereas factories offer flexibility. This makes factories suitable for:

  • Encapsulating implementation details
  • Expanding objects dynamically
  • Ad hoc compositions of behavior

And classes preferable for:

  • Rigid taxonomies of objects/roles
  • Base class abstractions
  • Polymorphic hierarchies

Consider balancing these factors when weighing factories versus classes:

Factories Classes
Declarative relations Weaker Stronger
Structure rigidity Lower Higher
Suitable complexity Low-Medium High
Object customization Higher Lower

Functional Factories

Factories lend themselves well to functional style code due to lacking this.

We can build reusable factory modules with closures:

function createCounterCreator() {
  let index = 0;

  return {
    createCounter() {
     return {
       getCount() {
         return index++; 
       }
     }
    }
  }
}

// usage

const counters = createCounterCreator();

const c1 = counters.createCounter(); 
const c2 = counters.createCounter();

c1.getCount(); // 0
c2.getCount(); // 1

The factory closure retains state across instances.

We can further enforce immutability by returning copies:

function clone(obj) {
  return JSON.parse(JSON.stringify(obj)) 
}

function createVector(x, y) {
  return clone({ x, y }); 
} 

const a = createVector(10, 20);
const b = createVector(10, 20); 

a === b; // false

Now vectors cannot be mutated in place.

These examples demonstrate factory synergy with functional programming principles.

Beyond JavaScript

Other languages have their own factory implementations:

Python

Returns class instances from factory methods:

def create_person(name):
  return Person(name)

C#

Utilizes dedicated Factory class:

public class PersonFactory {
  public static Person CreatePerson(string name) {
    return new Person {
      Name = name  
    };
  } 
}

TypeScript

Factory functions but with types:

interface Person {

  name: string

}

function createPerson(name: string): Person {

  return {
    name
  }

}

createPerson(123); // errors

These examples showcase idiomatic factories in other popular languages.

Architecting Factory Logic

When building significant logic with factories, consider separating concerns:

/**
 * Factory abstraction
 */

function createUserFactory() {

  let id = 0;

  return {
    createUser(name) {
      // reuse encapsulated logic 
    }
  }

}


/**
 * Application code
 */

const userFactory = createUserFactory();

const user = userFactory.createUser(‘Bob‘);

This moves factory state management away from application code.

We can further abstract logic into plugins:

functionreportingPlugin(userFactory) {

  function createUser(name) {

    let user = userFactory.createUser(name); 

    // custom logic...

    return user;

  }

  return { createUser };

}

const plugin = reportingPlugin(createUserFactory()); 

Keeps factory concerns modular.

These patterns demonstrate scaling factories by separating responsibilities.

Conclusion

Factory functions enable flexible and customizable construction of objects in JavaScript. Key strengths like encapsulation, dynamic prototypes, and simplicity make them suitable for:

  • Domains requiring high variance
  • Disposable objects
  • Custom compositions

Factory class blends and functional factories also showcase the versatility of the pattern.

Understanding how to leverage factories along with rigid hierarchies of classes allows managing code complexity. Factories provide an indispensable dynamic tool for crafting scalable and modular systems.

Similar Posts