Loading learning content...
In Hollywood, aspiring actors are often told after an audition: "Don't call us, we'll call you." The casting director controls the process—they'll reach out when they need the actor, not the other way around.
This phrase captures a profound design principle that underlies the Template Method Pattern and many other aspects of modern software architecture. In object-oriented design, we call this the Hollywood Principle: high-level components should control the flow, calling low-level components as needed, rather than low-level components reaching up to high-level ones.
By the end of this page, you will understand the Hollywood Principle, why it creates better software architectures, and how it manifests in the Template Method Pattern. You'll also see how this principle extends beyond Template Method to frameworks, callbacks, and dependency injection.
The Hollywood Principle is a design guideline that can be stated formally as:
High-level modules should not depend on low-level modules calling them. Instead, high-level modules should call low-level modules at the appropriate time.
Or more simply:
"Don't call us, we'll call you."
This principle defines a specific control flow relationship between components:
The key insight:
When high-level modules control the flow, they can enforce invariants, ensure correct sequencing, and maintain system-wide consistency. When low-level modules drive the flow, chaos ensues—each component might call others in different orders, violate assumptions, or create circular dependencies.
The Hollywood Principle is a specific application of Inversion of Control (IoC). Traditional programming has the application code calling library code. IoC inverts this: framework/library code calls application code. You provide implementations; the framework decides when to use them.
The Template Method Pattern is the quintessential example of the Hollywood Principle in object-oriented design. Let's trace exactly how control flows to see the principle in action:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
abstract class DocumentExporter { // HIGH-LEVEL COMPONENT: The template method // This method CONTROLS the flow public export(source: DataSource, filename: string): void { // The parent class CALLS the subclass at specific points // ↓ "We'll call you" this.beforeExport(); // Call #1: "Hey subclass, prepare" const data = this.gatherData(source); if (!this.validateData(data)) { // Call #2: "Validate this for us" return; } const stream = this.openStream(filename); this.writeHeader(stream); // Call #3: "Write your header" this.writeBody(stream, data); // Call #4: "Write your content" this.writeFooter(stream); // Call #5: "Write your footer" stream.close(); this.afterExport(filename); // Call #6: "Do your cleanup" this.notifyCompletion(filename); } // Abstract methods: The "you" that we'll call protected abstract writeHeader(stream: OutputStream): void; protected abstract writeBody(stream: OutputStream, data: EnrichedData): void; protected abstract writeFooter(stream: OutputStream): void; // Hooks: Optional "you" that we might call protected beforeExport(): void {} protected afterExport(filename: string): void {} protected validateData(data: EnrichedData): boolean { return true; }} // LOW-LEVEL COMPONENT: The concrete subclass// This class WAITS to be calledclass HtmlExporter extends DocumentExporter { // "Don't call us" — these methods don't call export() // They just implement what they're asked to implement protected writeHeader(stream: OutputStream): void { // I don't decide WHEN this runs // I only decide WHAT happens when called stream.write("<!DOCTYPE html>\n<html>..."); } protected writeBody(stream: OutputStream, data: EnrichedData): void { // Same here: I implement, parent decides when for (const record of data.records) { stream.write(`<article>${record.content}</article>\n`); } } protected writeFooter(stream: OutputStream): void { stream.write("</body></html>\n"); }}The flow is always top-down:
Client → calls → export()
│
DocumentExporter (high-level)
│
┌───────────┼───────────┐
↓ ↓ ↓
writeHeader writeBody writeFooter
│ │ │
└───────────┼───────────┘
│
HtmlExporter (low-level)
The HtmlExporter never reaches up to call methods on DocumentExporter or to control when its own methods run. It simply waits to be called, providing implementations that the parent orchestrates.
In Template Method, the abstract class (parent) is the high-level component. It defines the algorithm and controls when each step executes. The concrete class (child) is the low-level component. It provides implementations but doesn't control flow. This is the opposite of typical subclass behavior where children extend and call up to parents.
The Hollywood Principle isn't an arbitrary rule—it solves real architectural problems. Let's examine why this inversion of control creates better software:
When high-level components control the flow, they can enforce the algorithm's contract—ensuring preconditions, postconditions, and invariants are maintained.
12345678910111213141516171819202122232425262728293031
abstract class TransactionProcessor { // The high-level component enforces the transaction contract public processTransaction(transaction: Transaction): void { // Precondition: transaction must be validated this.validateTransaction(transaction); // Invariant: must acquire lock before modification const lock = this.acquireLock(transaction.id); try { // The subclass implements this, but within our controlled context this.executeTransaction(transaction); // Postcondition: must commit after successful execution this.commitTransaction(transaction); } catch (error) { // Invariant: must rollback on failure this.rollbackTransaction(transaction, error); } finally { // Invariant: must always release lock this.releaseLock(lock); } // Contract: logging happens after completion this.logTransactionResult(transaction); } // Subclasses cannot skip validation, forget to release lock, // or forget to commit/rollback. The contract is enforced. protected abstract executeTransaction(transaction: Transaction): void;}Low-level components don't need to know about high-level components. They implement simple, focused methods without understanding the larger context. This reduces coupling and increases cohesion.
123456789101112131415161718192021222324252627282930313233343536373839404142434445
// WITHOUT Hollywood Principle: High coupling// ─────────────────────────────────────────class CsvParser { parse(content: string): void { // Low-level component calls high-level components const dataProcessor = new DataProcessor(); // Knows about DataProcessor const validator = new Validator(); // Knows about Validator const storage = new StorageService(); // Knows about StorageService const lines = content.split('\n'); const parsed = lines.map(line => line.split(',')); // Parser controls the flow and depends on many components validator.validate(parsed); const processed = dataProcessor.process(parsed); storage.save(processed); }} // WITH Hollywood Principle: Low coupling// ─────────────────────────────────────abstract class DataPipeline { // High-level controls flow public run(input: string): void { const raw = this.parse(input); // "We'll call you for parsing" const validated = this.validate(raw); // "We'll call you for validation" const processed = this.transform(validated); // "We'll call you for transform" this.store(processed); // "We'll call you for storage" } protected abstract parse(input: string): RawData; protected abstract validate(raw: RawData): ValidatedData; protected abstract transform(data: ValidatedData): ProcessedData; protected abstract store(data: ProcessedData): void;} // Each low-level component knows only its own responsibilityclass CsvParser extends DataPipeline { protected parse(input: string): RawData { // Only knows about CSV parsing // Doesn't know about validation, transformation, or storage return input.split('\n').map(line => line.split(',')); } // ... other methods}Because low-level components don't control flow or depend on other components, they're easy to test in isolation. You can test each primitive operation independently.
123456789101112131415161718192021222324252627282930313233343536373839
// Testing a Template Method implementation is straightforwarddescribe('HtmlExporter', () => { describe('writeHeader', () => { it('should write valid HTML5 doctype', () => { const exporter = new HtmlExporter(mockEventBus); const mockStream = new MockOutputStream(); // Test just the header method — isolated from the algorithm exporter['writeHeader'](mockStream); expect(mockStream.content).toContain('<!DOCTYPE html>'); expect(mockStream.content).toContain('<html'); }); }); describe('writeBody', () => { it('should create article for each record', () => { const exporter = new HtmlExporter(mockEventBus); const mockStream = new MockOutputStream(); const data: EnrichedData = { records: [ { id: '1', content: 'First', processed: true }, { id: '2', content: 'Second', processed: true }, ] }; // Test body rendering — isolated from header/footer exporter['writeBody'](mockStream, data); expect(mockStream.content).toContain('<article>'); expect(mockStream.content.match(/<article>/g)).toHaveLength(2); }); });}); // Compare to testing without Hollywood Principle:// - Would need to mock DataProcessor, Validator, StorageService// - Would need to test the entire flow at once// - Failures could be in any componentBecause low-level components are simple and focused, creating new variations is straightforward. You implement the required methods without understanding the complex orchestration logic.
The Hollywood Principle is the foundation of framework design. A framework provides the high-level control structure; your application code provides the low-level implementations. The framework calls your code at the right time—you never call the framework's main loop.
While the Template Method Pattern is the most direct embodiment of the Hollywood Principle, this design philosophy appears throughout modern software architecture. Understanding these manifestations deepens your comprehension of the principle:
Callbacks are the functional programming equivalent of the Hollywood Principle. You provide a function; the framework calls it when appropriate.
12345678910111213141516171819202122232425
// Node.js file reading — Hollywood Principle with callbacksimport * as fs from 'fs'; // You provide the callback (the "you" we'll call)fs.readFile('data.txt', 'utf8', (err, data) => { // Don't call fs.readFile — it calls YOU when file is ready if (err) throw err; console.log(data);}); // Express.js middleware — framework calls your handlersapp.get('/users', (req, res) => { // Don't call Express — Express calls YOU when request arrives res.json({ users: ['Alice', 'Bob'] });}); // Array.prototype.map — higher-order functionconst doubled = [1, 2, 3].map(x => x * 2);// You provide the transformer — Array calls it for each element // React components — framework controls renderingfunction UserProfile({ user }: { user: User }) { // Don't call render — React calls YOUR component when state changes return <div>{user.name}</div>;}Dependency Injection is the Hollywood Principle applied to object creation and wiring. You provide implementations; the container calls constructors and injects dependencies.
12345678910111213141516171819202122232425
// WITHOUT Hollywood Principle (Service Locator)class OrderService { private paymentService: PaymentService; constructor() { // You call the container to get dependencies this.paymentService = ServiceLocator.get(PaymentService); }} // WITH Hollywood Principle (Dependency Injection)class OrderService { constructor( // Container calls YOUR constructor, injecting dependencies private paymentService: PaymentService, private inventoryService: InventoryService, private notificationService: NotificationService ) { // "Don't call container, container calls you" }} // Container configuration — you register, container callscontainer.register(PaymentService, StripePaymentService);container.register(OrderService); // Container will inject dependenciesWeb frameworks, game engines, and UI libraries all embody the Hollywood Principle through lifecycle hooks. You implement the hooks; the framework calls them.
12345678910111213141516171819202122232425262728293031323334353637383940
// React Component Lifecycle — Hollywood Principleclass UserDashboard extends React.Component<Props, State> { // React calls these methods at appropriate times componentDidMount(): void { // "We'll call you when component mounts" this.fetchUserData(); } componentDidUpdate(prevProps: Props): void { // "We'll call you when props/state change" if (prevProps.userId !== this.props.userId) { this.fetchUserData(); } } componentWillUnmount(): void { // "We'll call you before unmount" this.cancelPendingRequests(); } render(): JSX.Element { // "We'll call you when we need your UI" return <div>...</div>; }} // Angular — Same pattern@Component({ selector: 'user-dashboard', template: '...' })class UserDashboard implements OnInit, OnDestroy { ngOnInit(): void { /* Angular calls this */ } ngOnDestroy(): void { /* Angular calls this */ }} // Unity Game Engine — Same patternclass PlayerController : MonoBehaviour { void Start() { /* Unity calls on startup */ } void Update() { /* Unity calls every frame */ } void OnCollisionEnter() { /* Unity calls on physics events */ }}Plugin systems are built on the Hollywood Principle. The host application defines extension points; plugins provide implementations that the host calls.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
// Plugin interface — what the host will callinterface EditorPlugin { id: string; activate(context: PluginContext): void; deactivate(): void; contributeCommands(): Command[]; contributeMenus(): MenuItem[];} // Plugin implementation — waits to be calledclass MarkdownPreviewPlugin implements EditorPlugin { id = 'markdown-preview'; activate(context: PluginContext): void { // "Host calls me when user enables plugin" this.registerPreviewPanel(context); } deactivate(): void { // "Host calls me when user disables plugin" this.unregisterPreviewPanel(); } contributeCommands(): Command[] { // "Host calls me to discover my commands" return [ { id: 'markdown.preview', handler: () => this.showPreview() } ]; } contributeMenus(): MenuItem[] { // "Host calls me to discover my menu items" return [ { label: 'Markdown Preview', command: 'markdown.preview' } ]; }} // Host application orchestrates everythingclass Editor { private plugins: EditorPlugin[] = []; loadPlugin(plugin: EditorPlugin): void { this.plugins.push(plugin); // Host controls when plugin methods are called plugin.activate(this.createContext()); const commands = plugin.contributeCommands(); this.commandRegistry.registerAll(commands); const menus = plugin.contributeMenus(); this.menuBar.addItems(menus); }}| Domain | High-Level (Caller) | Low-Level (Called) | Extension Point |
|---|---|---|---|
| Template Method | Abstract class | Concrete subclass | Abstract/hook methods |
| Callbacks | Library/runtime | Application function | Callback parameter |
| Dependency Injection | IoC container | Service implementations | Constructor parameters |
| Framework Lifecycle | Framework | Component/controller | Lifecycle methods |
| Plugin Architecture | Host application | Plugin | Plugin interface |
| Event Systems | Event dispatcher | Event handlers | Event subscriptions |
The Template Method Pattern's use of inheritance is fundamentally different from traditional subclassing. Understanding this distinction is crucial for applying the pattern correctly:
In typical inheritance, subclasses extend parent behavior by calling super methods. The subclass initiates the call to the parent.
1234567891011121314151617181920212223242526272829303132333435
// Traditional inheritance: subclass calls parentclass Animal { makeSound(): void { console.log("Generic animal sound"); } move(): void { console.log("Generic movement"); }} class Dog extends Animal { makeSound(): void { // Subclass CALLS parent super.makeSound(); // First call parent console.log("Bark!"); // Then add specific behavior } move(): void { console.log("Running on four legs"); super.move(); // Maybe call parent somewhere } playFetch(): void { // Subclass can add new methods // and call them whenever it wants this.move(); console.log("Fetching ball!"); this.makeSound(); }} // Usage: client calls subclass, subclass controls everythingconst dog = new Dog();dog.playFetch(); // Subclass method orchestratesIn the Template Method Pattern, the relationship is inverted. The parent class calls the subclass methods at specific points in the algorithm.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
// Template Method: parent calls subclassabstract class AnimalBehavior { // Template method: parent orchestrates public performDailyRoutine(): void { this.wakeUp(); this.eat(); // Abstract — parent CALLS subclass this.move(); // Abstract — parent CALLS subclass this.makeSound(); // Abstract — parent CALLS subclass this.sleep(); } private wakeUp(): void { console.log("Waking up..."); } private sleep(): void { console.log("Going to sleep..."); } // Parent defines what to call; subclass defines how protected abstract eat(): void; protected abstract move(): void; protected abstract makeSound(): void;} class DogBehavior extends AnimalBehavior { // Subclass just implements what's asked // It doesn't control when these run protected eat(): void { console.log("Eating kibble from bowl"); } protected move(): void { console.log("Running on four legs"); } protected makeSound(): void { console.log("Bark!"); } // NO playFetch() method that calls everything // The template method IS the orchestration} // Usage: client calls template method, parent controls everythingconst dog = new DogBehavior();dog.performDailyRoutine(); // Parent method orchestrates| Aspect | Traditional Subclassing | Template Method |
|---|---|---|
| Control direction | Subclass → Parent (calls super) | Parent → Subclass (calls abstract) |
| Orchestration | Subclass orchestrates | Parent orchestrates |
| Method visibility | Often public/protected | Usually protected (internal) |
| Adding methods | Subclass adds freely | Subclass implements what's declared |
| Algorithm control | Subclass can change anything | Subclass customizes specific steps |
| Coupling | Higher (subclass knows parent) | Lower (subclass knows interface) |
| Contract enforcement | Weak (subclass can skip super) | Strong (parent controls all calls) |
Template Method and traditional subclassing serve different purposes. Use Template Method when you have an algorithm with varying steps that must maintain a specific structure. Use traditional subclassing when you're genuinely extending or specializing behavior without structural constraints.
Adopting the Hollywood Principle has profound implications for how you design systems. Here are the key considerations:
Systems following the Hollywood Principle have clear high-level and low-level layers. Dependencies flow downward; calls flow downward. This creates clean architecture.
12345678910111213141516171819202122232425262728
Hollywood Principle Architecture:═══════════════════════════════════════════════════ ┌─────────────────────────────────────────────────┐│ HIGH-LEVEL LAYER ││ (Controls flow, knows the algorithm) ││ ┌─────────────────────────────────────────────┐││ │ Template Method / Framework / Orchestrator │││ │ - Defines the skeleton │││ │ - Calls low-level components │││ │ - Enforces contracts │││ └───────────────────┬─────────────────────────┘│└──────────────────────┼──────────────────────────┘ │ calls ↓┌──────────────────────┼──────────────────────────┐│ LOW-LEVEL LAYER ││ (Provides implementations, focused) ││ ┌───────────┐ ┌───────────┐ ┌───────────┐ ││ │ Concrete │ │ Concrete │ │ Concrete │ ││ │ Impl A │ │ Impl B │ │ Impl C │ ││ │ │ │ │ │ │ ││ │ writeHdr()│ │ writeHdr()│ │ writeHdr()│ ││ │ writeBody │ │ writeBody │ │ writeBody │ ││ │ writeFtr()│ │ writeFtr()│ │ writeFtr()│ ││ └───────────┘ └───────────┘ └───────────┘ │└─────────────────────────────────────────────────┘ Control flows DOWN only. Never UP.Low-level components become highly focused. They implement specific operations without understanding the larger context. This increases cohesion and simplifies testing.
With the Hollywood Principle, extension points are explicit. You know exactly where customization happens because the high-level layer explicitly defines the calls it makes.
123456789101112131415161718192021222324252627282930313233
abstract class OrderProcessor { // The template method makes extension points obvious public async processOrder(order: Order): Promise<Result> { // Extension point: validation await this.validateOrder(order); // Extension point: inventory check await this.checkInventory(order); // Extension point: payment processing const payment = await this.processPayment(order); // Extension point: fulfillment await this.fulfillOrder(order, payment); // Extension point: notification await this.notifyCustomer(order); return { success: true, orderId: order.id }; } // Clear, documented extension points protected abstract validateOrder(order: Order): Promise<void>; protected abstract checkInventory(order: Order): Promise<void>; protected abstract processPayment(order: Order): Promise<Payment>; protected abstract fulfillOrder(order: Order, payment: Payment): Promise<void>; // Optional extension point with default protected async notifyCustomer(order: Order): Promise<void> { // Default: send email await this.emailService.send(order.customerEmail, 'Order confirmed'); }}When you follow the Hollywood Principle, the high-level template method serves as documentation. Reading it tells you the algorithm structure and all customization points. The code is self-documenting architecture.
Violations of the Hollywood Principle create architectural problems. Learning to recognize these violations helps you maintain clean designs:
1234567891011121314151617181920212223242526272829303132333435363738
// VIOLATION 1: Low-level calling high-levelclass UserRepository { constructor() { // Repository (low-level) imports Controller (high-level) this.controller = new UserController(); // ❌ WRONG } saveUser(user: User): void { this.db.save(user); this.controller.notifyUserCreated(user); // ❌ Calling up }} // VIOLATION 2: Subclass calling template methodclass HtmlExporter extends DocumentExporter { protected writeBody(stream: OutputStream, data: EnrichedData): void { if (data.records.length === 0) { // Subclass calls parent's orchestration method this.export(fallbackSource, 'fallback'); // ❌ WRONG return; } // ... }} // VIOLATION 3: Callback reaching backeventBus.on('userCreated', (user: User) => { // Callback reaches back to manipulate event bus eventBus.pauseEvents(); // ❌ Reaching into invoker processUser(user); eventBus.resumeEvents();}); // CORRECT: Callbacks are independenteventBus.on('userCreated', (user: User) => { // Just handle the event, don't manipulate invoker sendWelcomeEmail(user); // ✓ Focused, independent});A good heuristic: if you need to import a high-level module (framework, orchestrator, controller) into a low-level module (implementation, handler, repository), you're likely violating the Hollywood Principle. Dependencies should flow from high-level to low-level, never the reverse.
We've explored the Hollywood Principle—the design philosophy that underlies the Template Method Pattern and much of modern software architecture. Let's consolidate the key insights:
What's Next:
In the next page, we'll explore concrete use cases and real-world examples of the Template Method Pattern. We'll see how it's applied in frameworks like JUnit, Spring, and React, and work through detailed implementation scenarios that you can adapt to your own projects.
You now understand the Hollywood Principle and how it underlies the Template Method Pattern. You can recognize this principle in frameworks and libraries you use, and you can identify violations that lead to architectural problems. Next, we'll explore practical use cases and implementations.