Factory Functions and Module Pattern

Factory Functions

& The Module Pattern

CSCI 4513

Week 3, Lecture 5

Today's Learning Objectives

How Thor Got His Hammer

by Scott A. Mellor via TED-Ed

Norse Mythology Connection

The Treasures of the Dwarves

Dwarven workshops created legendary treasures using different crafting patterns, like constructors and factory functions create objects.

The Tale:

When Loki cut off Sif's golden hair, he sought the sons of Ivaldi to create magical replacements. These master craftsmen created not only golden hair but also Gungnir (Odin's spear) and Skidbladnir (Freyr's ship that folds into a pocket).

Loki then challenged the brothers Brokk and Eitri, who forged Gullinbursti (a glowing boar), Draupnir (a ring that creates new gold rings), and Mjolnir (Thor's mighty hammer). Each treasure was unique with its own properties and behaviors, yet all followed similar crafting patternsโ€”specialized workshops creating magical items through different implementations.

Workshops Create Treasures

// Constructor - blueprint for treasures
function DivineWeapon(name, wielder, power) {
    this.name = name;
    this.wielder = wielder;
    this.power = power;
}

// Factory Function - flexible creation
function createTreasure(type, owner) {
    return {
        type,
        owner,
        enchant() { return `${this.type} glows with magic`; }
    };
}

// Module Pattern - workshop secrets
const DwarvenWorkshop = (function() {
    const secretTechnique = "Ancient magic";
    return {
        forgeTreasure(specs) { /* uses secret */ }
    };
})();

Try It: Browser Console

After pasting the code above into your console, test each pattern:

  • Constructor:
    const mjolnir = new DivineWeapon("Mjolnir", "Thor", 100);
    console.log(mjolnir.name);
  • Factory:
    const ring = createTreasure("Ring", "Odin");
    ring.enchant();
  • Module:
    DwarvenWorkshop.forgeTreasure({name: "Gungnir"});
    console.log(DwarvenWorkshop.secretTechnique); โ€” This returns undefined (it's private!)

Reflection Questions

Click each card to reveal thoughts!

โ“ Question 1

Two workshops created different treasures using their own methods. When would you use a factory function vs a constructor?

โ“ Question 2

Each treasure had unique properties. How does this parallel object creation patterns?

โ“ Question 3

The workshops kept some techniques secret. How does the module pattern provide encapsulation?

Understanding Scope

"Where is a variable available to me?"

Global Scope

  • Outside any functions or blocks
  • Available everywhere
  • Use sparingly!

Local Scope

  • Inside functions or blocks
  • Limited availability
  • Safer and preferred

Scoping asks: "Where is a certain variable available to me?" - it indicates the current context of a variable.

var vs let/const

// var - Function scoped (pre-ES6)
function oldWay() {
  var x = 1;
  if (true) {
    var x = 2;  // Same variable!
    console.log(x);  // 2
  }
  console.log(x);  // 2 - var leaked out
}

// let/const - Block scoped (ES6+)
function newWay() {
  let x = 1;
  if (true) {
    let x = 2;  // Different variable!
    console.log(x);  // 2
  }
  console.log(x);  // 1 - let stayed in block
}

var - Function-scoped. Redeclaring inside a block overwrites the outer variable.

let - Block-scoped. Creates a new variable confined to its block { }.

The Bug - With var, both x declarations refer to the same variable, so the second overwrites the first.

This is why var was replacedโ€”accidental overwrites caused hard-to-find bugs.

๐Ÿ’ก Best Practice: Use const by default, let when you need to reassign, avoid var.

Scope in Action

let globalAge = 23; // Global variable

function printAge(age) {
  var varAge = 34; // Function scoped

  if (age > 0) {
    // Block-scoped variable
    const constAge = age * 2;
    console.log(constAge);  // Works!
  }

  // ERROR! constAge only exists in if block
  console.log(constAge);
}

printAge(globalAge);

// ERROR! varAge only exists in function
console.log(varAge);
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  GLOBAL SCOPE               โ”‚
โ”‚  โ””โ”€ globalAge โœ“             โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚
โ”‚  โ”‚  FUNCTION SCOPE         โ”‚โ”‚
โ”‚  โ”‚  โ””โ”€ varAge โœ“            โ”‚โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚โ”‚
โ”‚  โ”‚  โ”‚  BLOCK SCOPE        โ”‚โ”‚โ”‚
โ”‚  โ”‚  โ”‚  โ””โ”€ constAge โœ“      โ”‚โ”‚โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚โ”‚
โ”‚  โ”‚  constAge โœ— (not here) โ”‚โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚
โ”‚  varAge โœ— (not here)        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Scope Levels - Variables are only accessible within their scope and nested inner scopes.

Closures Aren't Scary

They're Actually Powerful!

function makeAdding(firstNumber) {
  // "first" is scoped within makeAdding
  const first = firstNumber;

  return function resulting(secondNumber) {
    // "second" is scoped within resulting
    const second = secondNumber;
    return first + second;  // Still access first!
  }
}

const add5 = makeAdding(5);
console.log(add5(2));  // 7
console.log(add5(10)); // 15

Outer function - Creates first and returns an inner function.

Inner function - "Closes over" first, keeping it alive even after outer function returns.

The Magic - add5 remembers first = 5 and can use it later!

This is a closure: a function bundled with its lexical environment.

How Closures Work

Definition

A closure is the combination of a function and the surrounding state (lexical environment) in which it was declared. This includes any local variables that were in scope at the time.

Problems with Constructors

โš ๏ธ Result: Constructors have become unpopular in favor of Factory Functions!

Factory Functions ๐Ÿญ

A Better Pattern

Constructor

const User = function(name) {
  this.name = name;
  this.discord = "@" + name;
}

// Need 'new' keyword!
const user = new User("josh");

Issues: Requires new, uses this binding, forgetting new causes bugs.

Factory Function

function createUser(name) {
  const discord = "@" + name;
  return { name, discord };
}

// Just call it!
const user = createUser("josh");

Benefits: No new, no this, just returns a plain object.

๐Ÿ’ก Key Difference: Factories return objects directly, no new needed!

Object Shorthand Notation

ES6 Made Objects Cleaner

const name = "Bob";
const age = 28;
const color = "red";

// The old way (redundant)
const oldObject = { name: name, age: age, color: color };

// The new way (clean!)
const newObject = { name, age, color };

// Bonus: Great for console.log!
console.log(name, age, color);
// Bob 28 red (messy)

console.log({ name, age, color });
// { name: "Bob", age: 28, color: "red" }

{ name: name } โ†’ { name }
When property name matches variable name, just use the name once.

Why It Matters - Factory functions return objects constantly. Shorthand makes them readable.

Debug Tip - Wrapping values in {} shows labeled output in console.

Destructuring

Unpack Values from Objects/Arrays

// Object destructuring
const obj = { a: 1, b: 2 };
const { a, b } = obj;
// Creates variables a and b

// Array destructuring
const array = [1, 2, 3, 4, 5];
const [zerothEle, firstEle] = array;
// Creates variables for first two elements

// Destructuring in function parameters
function createPlayer({ name, level }) {
  return { name, level };
}
createPlayer({ name: "Link", level: 50 });

{ a, b } = obj - Extract properties into variables with matching names.

[x, y] = arr - Extract array elements by position.

Parameter Destructuring - Unpack object properties directly in function signature. Very common in factories!

Destructuring is the opposite of shorthand: extract values instead of creating objects.

Private Variables with Closures

function createUser(name) {
  const discordName = "@" + name;

  // Private variable - not returned!
  let reputation = 0;

  // Methods using closure
  const getReputation = () => reputation;
  const giveReputation = () => reputation++;

  return { name, discordName,
           getReputation, giveReputation };
}

const josh = createUser("josh");
josh.giveReputation();
josh.giveReputation();

console.log(josh.reputation);      // undefined!
console.log(josh.getReputation()); // 2
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  createUser closure          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ reputation (private)   โ”‚  โ”‚
โ”‚  โ”‚ getReputation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”ผโ”€โ–บ returned
โ”‚  โ”‚ giveReputation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”ผโ”€โ–บ returned
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Trick - reputation exists in the closure but is NOT in the returned object.

Access Control - Only the returned methods can read/modify reputation.

Why Private Variables?

Encapsulation Benefits

  • ๐Ÿ”’ Protection: Can't accidentally set reputation to -18000
  • ๐Ÿงน Cleaner API: Only expose what users need
  • ๐ŸŽฏ Controlled Access: Validation in getter/setter methods
  • ๐Ÿ“ฆ Implementation Hiding: Change internals without breaking code
๐Ÿ’ก Rule of Thumb: Make things private by default, expose only what's necessary!

Prototypal Inheritance with Factories

Extending Objects

// Base factory
function createUser(name) {
  const getReputation = () => reputation;
  const giveReputation = () => reputation++;
  let reputation = 0;

  return { name, getReputation, giveReputation };
}

// Extended factory
function createPlayer(name, level) {
  const { getReputation, giveReputation } = createUser(name);

  const increaseLevel = () => level++;

  return { name, getReputation, giveReputation,
           increaseLevel };
}
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   createUser    โ”‚
โ”‚  โ”œโ”€ name        โ”‚
โ”‚  โ”œโ”€ getReputation
โ”‚  โ””โ”€ giveReputation
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ”‚ destructure
         โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  createPlayer   โ”‚
โ”‚  โ”œโ”€ name        โ”‚
โ”‚  โ”œโ”€ getReputation  (from User)
โ”‚  โ”œโ”€ giveReputation (from User)
โ”‚  โ””โ”€ increaseLevel  (new)
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Composition - Call the base factory, destructure its methods, then add new functionality.

Inheritance with Object.assign

function createUser(name) {
  const getReputation = () => reputation;
  const giveReputation = () => reputation++;
  let reputation = 0;

  return { name, getReputation, giveReputation };
}

function createPlayer(name, level) {
  const user = createUser(name);

  const increaseLevel = () => level++;

  // Merge user properties with new ones
  return Object.assign({}, user, { increaseLevel });
}

const player = createPlayer("Link", 50);
player.giveReputation();
player.increaseLevel();

Object.assign(target, ...sources)
Copies all properties from source objects into target.

Object.assign(
  {},              // empty target
  user,            // copies user props
  { increaseLevel} // adds new props
)
// โ†’ merged object with all properties

Alternative Syntax - Same result as destructuring, but copies ALL properties without listing them.

The Module Pattern: IIFEs

Immediately Invoked Function Expressions

// Regular factory - can call multiple times
function createCalculator() {
  const add = (a, b) => a + b;
  const sub = (a, b) => a - b;
  return { add, sub };
}

// IIFE - runs once immediately
const calculator = (function() {
  const add = (a, b) => a + b;
  const sub = (a, b) => a - b;
  return { add, sub };
})();  // <-- Note the () at the end!

calculator.add(3, 5);  // 8

(function() { ... }) - Wrap function in parentheses to make it an expression.

() at the end - Immediately invoke (call) that function.

Result - calculator holds the returned object, NOT the function. There's only ever one instance.

IIFE = Immediately Invoked Function Expression

Why Use the Module Pattern?

When You Only Need One Instance

  • ๐ŸŽฏ Single instance (gameboard, display controller)
  • ๐Ÿ”’ Encapsulate related functionality
  • ๐Ÿงน Hide implementation details
  • ๐Ÿ“› Create namespaces

Pattern: Wrap factory in parentheses, immediately invoke it with ()

Module Pattern in Action

const calculator = (function() {
  // Private helper function
  const validate = (a, b) => {
    if (typeof a !== 'number' ||
        typeof b !== 'number') {
      throw new Error('Must be numbers');
    }
  };

  // Public API
  const add = (a, b) => {
    validate(a, b);
    return a + b;
  };

  const sub = (a, b) => {
    validate(a, b);
    return a - b;
  };

  return { add, sub };  // Only these exposed
})();

calculator.add(3, 5);      // 8
calculator.validate(1, 2); // undefined!
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   calculator module     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ validate (PRIVATE)โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ add โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”ผโ”€โ–บ PUBLIC
โ”‚  โ”‚ sub โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”ผโ”€โ–บ PUBLIC
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Private Helper - validate is used internally but never exposed to outside code.

Public API - Only add and sub are returned, so only they can be called.

Encapsulation

Bundle Code with Selective Access

Encapsulation: Bundling data and code into a single unit with selective access to the internals.

Namespacing

Avoiding Naming Collisions

// Without namespacing - DISASTER!
function add(a, b) { return a + b; }
function add(a, b) { return String(a) + String(b); }
// Which add() are we calling?

// With namespacing - CLEAR!
const mathOps = (function() {
  const add = (a, b) => a + b;
  return { add };
})();

const stringOps = (function() {
  const add = (a, b) => String(a) + String(b);
  return { add };
})();

mathOps.add(2, 3);     // 5
stringOps.add(2, 3);   // "23"

The Problem - Two functions with the same name? The second one overwrites the first!

Global Scope:
โ”œโ”€ mathOps.add()     โ†’ numeric
โ””โ”€ stringOps.add()   โ†’ string concat

Both "add" functions exist safely!

Namespacing - Group related functions under a module name. Each module has its own isolated scope.

Project: Tic Tac Toe

Apply Factory Functions & Module Pattern!

Project Goals

  • ๐ŸŽฎ Build a playable Tic Tac Toe game in the browser
  • ๐Ÿญ Use factory functions for players
  • ๐Ÿ“ฆ Use module pattern for gameboard & game controller
  • ๐ŸŒ Minimize global code - everything tucked into objects!

Tic Tac Toe Structure

// Module pattern for gameboard (only need one)
const Gameboard = (function() {
  const board = ['', '', '', '', '', '', '', '', ''];

  const getBoard = () => board;
  const setMark = (index, mark) => {
    board[index] = mark;
  };
  const reset = () => board.fill('');

  return { getBoard, setMark, reset };
})();

// Factory for players (need multiple)
const createPlayer = (name, mark) => {
  return { name, mark };
};

// Module pattern for game controller
const GameController = (function() {
  // Game logic here
})();
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Gameboard (Module/IIFE)    โ”‚
โ”‚  โ””โ”€ Single instance         โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  GameController (Module)    โ”‚
โ”‚  โ””โ”€ Single instance         โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  createPlayer (Factory)     โ”‚
โ”‚  โ”œโ”€โ–บ Player 1 { name, mark }โ”‚
โ”‚  โ””โ”€โ–บ Player 2 { name, mark }โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

IIFE - One gameboard, one controller. Created immediately.

Factory - Need two players with different names/marks.

Project Implementation Steps

  1. Set up HTML/CSS/JS files and Git repo
  2. Create Gameboard module with array storage
  3. Create createPlayer factory function
  4. Create GameController module for game flow
  5. Get it working in console first!
  6. Check for wins (3-in-a-row) and ties
  7. Create display controller for DOM logic
  8. Add click handlers for board squares
  9. Add UI for player names and start/restart button

Organizing Your Code

Use Factories When:

  • Need multiple instances
  • Players
  • Maybe cells?

Use Modules (IIFE) When:

  • Only need one instance
  • Gameboard
  • Game controller
  • Display controller
๐Ÿ’ก Key Tip: Think about where each piece of logic belongs. Each function should fit logically in game, player, or gameboard objects!

Console First, Then DOM!

โš ๏ธ Important Strategy

  • ๐ŸŽฏ Focus on game logic first in the console
  • ๐Ÿ” Test win conditions and tie detection
  • ๐ŸŽฎ Call functions manually to play games
  • โœ… Make sure everything works before touching DOM
  • ๐ŸŽจ Then add display logic in separate object

Why? Separating logic from presentation makes debugging much easier!

Helpful Resources

Recommended Reading

"Building a House from the Inside Out"

Great article that shows how to approach this project and organize your code structure. Highly applicable to Tic Tac Toe!

๐Ÿ’ก Brainstorm First: Spend time planning your code organization - it will save you hours later!

Today's Takeaways

You Can Now:

๐Ÿ“… Next Class: Classes

๐Ÿ“ Homework: Tic Tac Toe