A dispatcher method matches a request type or command name against a chain of `if`/`else` branches, each calling a handler method on the same class. The dispatcher grows wider with every new command; the class grows in proportion; the dispatch table and the handlers live tangled in one file.
Each handler is a Command object with an `execute(payload)` method. The dispatcher holds a registry mapping command name to Command instance; dispatch is a one-line lookup-and-invoke. New commands ship by registering a new Command — the dispatcher never changes.
Before the refactoring
// One processor with a long dispatch conditional plus all handler bodies.class CommandProcessor {execute(command, payload) {if (command === 'create-user') return this.createUser(payload);if (command === 'delete-user') return this.deleteUser(payload);if (command === 'reset-password') return this.resetPassword(payload);if (command === 'update-profile') return this.updateProfile(payload);throw new Error(`unknown command ${command}`);}createUser(payload) { /* ... */ }deleteUser(payload) { /* ... */ }resetPassword(payload) { /* ... */ }updateProfile(payload) { /* ... */ }}
After the refactoring
// Each handler is a Command object; the registry replaces the conditional.class CreateUserCommand {execute(payload) { /* ... */ }}class DeleteUserCommand {execute(payload) { /* ... */ }}class ResetPasswordCommand {execute(payload) { /* ... */ }}class UpdateProfileCommand {execute(payload) { /* ... */ }}class CommandProcessor {constructor() {this.commands = new Map([['create-user', new CreateUserCommand()],['delete-user', new DeleteUserCommand()],['reset-password', new ResetPasswordCommand()],['update-profile', new UpdateProfileCommand()],]);}execute(command, payload) {const handler = this.commands.get(command);if (!handler) throw new Error(`unknown command ${command}`);return handler.execute(payload);}}
Conditional dispatchers are Shotgun-Surgery magnets — every new command edits the dispatcher and adds a method to the host class. Cross-command invariants (e.g., logging or auth checks) duplicate at every branch; subtle inconsistencies between handlers ship to runtime.
Command introduces a class per handler; what was one file becomes many. Logical cohesion (handlers that share helpers or share state) must be deliberately re-established via composition. Command queues and undo stacks become natural — useful when needed, ceremony when not.
Each handler is one cohesive class; the dispatcher is a few lines of generic code. Adding a command is one new file plus one registry entry; tests for a command target one class; cross-cutting concerns (logging, validation) can decorate the dispatcher rather than duplicating per branch.
A Command class per trivial action becomes ceremony tax — `class FooCommand { execute(x) { return foo(x); } }`. The pattern earns its keep when commands carry state (captured arguments, undo info), when there are many of them, or when cross-cutting concerns benefit from the uniform interface.