Files

358 lines
9.3 KiB
Markdown

# Module Pattern
> Encapsulate related functionality, data, and behavior into a single unit with a well-defined public API, hiding internal implementation details.
## Problem
You have a set of related utility functions and state (e.g., a string utility library with internal helpers). Without encapsulation, all functions and variables pollute the global namespace, internal helpers are exposed, and there is no clear boundary between public and private.
## Solution
Group related functionality into a module that exposes only a public API. Internal state and helper functions are hidden. In Python this is a module with `__all__`; in Go it's a package with unexported identifiers; in JavaScript it's a closure/IIFE or ES module with selective exports.
```
┌──────────────────────────────┐
│ StringUtils Module │
│ │
│ Public API: │
│ + capitalize(s) │
│ + slug(s) │
│ + word_count(s) │
│ + stats() │
│ │
│ Internal (hidden): │
│ - _call_count │
│ - _track_call() │
│ - _normalize_whitespace() │
└──────────────────────────────┘
```
## When to Use
- You want to group related functions and state with a clear public/private boundary.
- You need to avoid polluting the global namespace.
- You want to enforce encapsulation without full OOP class hierarchies.
- You are building a utility library or service layer.
## When to Avoid
- The functionality is a single function — just export the function directly.
- You need multiple instances with separate state — use a class or factory instead.
- The language has first-class module support and you'd just be wrapping it redundantly.
## Pseudocode
```
module StringUtils:
// Private state
_call_count = 0
// Private helper
function _track_call():
_call_count += 1
function _normalize_whitespace(s):
return collapse multiple spaces in s to single spaces, trimmed
// Public API
function capitalize(s):
_track_call()
return first letter uppercase + rest of s
function slug(s):
_track_call()
return _normalize_whitespace(s).lower().replace(" ", "-")
function word_count(s):
_track_call()
return number of words in _normalize_whitespace(s)
function stats():
return { calls: _call_count }
// Usage
print StringUtils.capitalize("hello world")
print StringUtils.slug("Hello Big World")
print StringUtils.word_count("one two three")
print StringUtils.stats()
```
## Python
```python
"""
string_utils module — demonstrates the Module pattern.
Only symbols listed in __all__ are part of the public API.
"""
# --- Private state ---
_call_count = 0
# --- Private helpers ---
def _track_call() -> None:
global _call_count
_call_count += 1
def _normalize_whitespace(s: str) -> str:
"""Collapse multiple spaces and strip."""
return " ".join(s.split())
# --- Public API ---
def capitalize(s: str) -> str:
"""Capitalize the first letter of the string."""
_track_call()
if not s:
return s
return s[0].upper() + s[1:]
def slug(s: str) -> str:
"""Convert to a URL-friendly slug."""
_track_call()
return _normalize_whitespace(s).lower().replace(" ", "-")
def word_count(s: str) -> int:
"""Count words in a string."""
_track_call()
normalized = _normalize_whitespace(s)
return len(normalized.split()) if normalized else 0
def stats() -> dict:
"""Return usage statistics."""
return {"total_calls": _call_count}
__all__ = ["capitalize", "slug", "word_count", "stats"]
if __name__ == "__main__":
print("=== Module Pattern: StringUtils ===")
print()
result1 = capitalize("hello world")
print(f"capitalize('hello world') => '{result1}'")
result2 = slug("Hello Big World")
print(f"slug('Hello Big World') => '{result2}'")
result3 = word_count(" one two three ")
print(f"word_count(' one two three ') => {result3}")
result4 = slug("Another Test String")
print(f"slug('Another Test String') => '{result4}'")
print(f"\nstats() => {stats()}")
# Show that private members exist but are conventionally hidden
print(f"\n--- Internal state (not part of public API) ---")
print(f"_call_count = {_call_count}")
print(f"_normalize_whitespace(' a b ') = '{_normalize_whitespace(' a b ')}'")
```
**Output:**
```
=== Module Pattern: StringUtils ===
capitalize('hello world') => 'Hello world'
slug('Hello Big World') => 'hello-big-world'
word_count(' one two three ') => 3
slug('Another Test String') => 'another-test-string'
stats() => {'total_calls': 4}
--- Internal state (not part of public API) ---
_call_count = 4
_normalize_whitespace(' a b ') = 'a b'
```
## Go
```go
package main
import (
"fmt"
"strings"
"unicode"
)
// ============================================================
// stringutils "module" — unexported identifiers are private.
// In a real project this would be its own package.
// ============================================================
// private state
var callCount int
// private helper
func trackCall() {
callCount++
}
func normalizeWhitespace(s string) string {
return strings.Join(strings.Fields(s), " ")
}
// --- Public API (exported) ---
func Capitalize(s string) string {
trackCall()
if len(s) == 0 {
return s
}
runes := []rune(s)
runes[0] = unicode.ToUpper(runes[0])
return string(runes)
}
func Slug(s string) string {
trackCall()
return strings.ReplaceAll(strings.ToLower(normalizeWhitespace(s)), " ", "-")
}
func WordCount(s string) int {
trackCall()
normalized := normalizeWhitespace(s)
if normalized == "" {
return 0
}
return len(strings.Fields(normalized))
}
type Stats struct {
TotalCalls int
}
func GetStats() Stats {
return Stats{TotalCalls: callCount}
}
// ============================================================
func main() {
fmt.Println("=== Module Pattern: StringUtils ===")
fmt.Println()
r1 := Capitalize("hello world")
fmt.Printf("Capitalize(\"hello world\") => '%s'\n", r1)
r2 := Slug("Hello Big World")
fmt.Printf("Slug(\"Hello Big World\") => '%s'\n", r2)
r3 := WordCount(" one two three ")
fmt.Printf("WordCount(\" one two three \") => %d\n", r3)
r4 := Slug("Another Test String")
fmt.Printf("Slug(\"Another Test String\") => '%s'\n", r4)
fmt.Printf("\nGetStats() => %+v\n", GetStats())
}
```
**Output:**
```
=== Module Pattern: StringUtils ===
Capitalize("hello world") => 'Hello world'
Slug("Hello Big World") => 'hello-big-world'
WordCount(" one two three ") => 3
Slug("Another Test String") => 'another-test-string'
GetStats() => {TotalCalls:4}
```
## JavaScript
```javascript
// Module pattern using an IIFE to create a closure with private scope.
const StringUtils = (() => {
// --- Private state ---
let callCount = 0;
// --- Private helpers ---
function trackCall() {
callCount++;
}
function normalizeWhitespace(s) {
return s.trim().replace(/\s+/g, " ");
}
// --- Public API ---
return {
capitalize(s) {
trackCall();
if (!s) return s;
return s.charAt(0).toUpperCase() + s.slice(1);
},
slug(s) {
trackCall();
return normalizeWhitespace(s).toLowerCase().replace(/ /g, "-");
},
wordCount(s) {
trackCall();
const normalized = normalizeWhitespace(s);
return normalized === "" ? 0 : normalized.split(" ").length;
},
stats() {
return { totalCalls: callCount };
},
};
})();
// --- Main ---
console.log("=== Module Pattern: StringUtils ===\n");
const r1 = StringUtils.capitalize("hello world");
console.log(`capitalize('hello world') => '${r1}'`);
const r2 = StringUtils.slug("Hello Big World");
console.log(`slug('Hello Big World') => '${r2}'`);
const r3 = StringUtils.wordCount(" one two three ");
console.log(`wordCount(' one two three ') => ${r3}`);
const r4 = StringUtils.slug("Another Test String");
console.log(`slug('Another Test String') => '${r4}'`);
console.log(`\nstats() => ${JSON.stringify(StringUtils.stats())}`);
// Demonstrate that internals are truly hidden
console.log(`\n--- Attempting to access private state ---`);
console.log(`StringUtils.callCount => ${StringUtils.callCount}`); // undefined
console.log(`StringUtils.trackCall => ${StringUtils.trackCall}`); // undefined
```
**Output:**
```
=== Module Pattern: StringUtils ===
capitalize('hello world') => 'Hello world'
slug('Hello Big World') => 'hello-big-world'
wordCount(' one two three ') => 3
slug('Another Test String') => 'another-test-string'
stats() => {"totalCalls":4}
--- Attempting to access private state ---
StringUtils.callCount => undefined
StringUtils.trackCall => undefined
```
## Related Patterns
- **Facade** — a facade provides a simplified interface over a subsystem; a module encapsulates a subsystem with explicit public/private boundaries.
- **Singleton** — a module is effectively a singleton in most module systems (loaded and cached once).
- **Namespace** — modules provide namespacing as a side effect, preventing name collisions in the global scope.