Design Patterns in JavaScript: Unlocking the Power of Code Structure

A code-dependent life form.
Search for a command to run...

A code-dependent life form.
No comments yet. Be the first to comment.
The first time I held my phone against my passport and watched an app pull my own photo off the chip, I had two reactions. First: that's really cool. Second: wait, how did it do that? So I went diggin

I just finished rebuilding our docs at docs.self.xyz, and the whole thing came from one assumption: every page now gets read twice. Once by a developer, and once by the agent sitting next to them. If

The final part of a series on agent harnesses. We started with the model-plus-harness idea, then built subagents, gave them a safe place to work, and wired up tools, memory, and workflows. This last p

Part four of a series on agent harnesses. The earlier pieces covered the model-plus-harness idea, subagents, and sandboxes. This one is about the three things that turn a capable agent into one that e

Part three of a series on agent harnesses. Part one introduced the model-plus-harness idea, and part two split work across subagents. This piece is about the layer where agents stop just talking and s

Design patterns are essential tools in software development that help us create flexible, maintainable, and reusable code. They provide solutions to common design problems and establish best practices for organizing and structuring our applications. In this blog post, we will explore five popular design patterns in JavaScript: the Null Object Pattern, Builder Pattern, Singleton Pattern, Facade Pattern, and Command Pattern. We'll discuss their purpose, and implementation, and provide code examples to illustrate their usage.
The Null Object Pattern is used to handle null or undefined values gracefully, avoiding null pointer exceptions. It provides a default object that exhibits the same behaviour as a non-null object, allowing us to work with an object without the need for explicit null checks.
Code Example:
class NullObject {
doSomething() {
// Do nothing or provide a default behavior
}
}
class NonNullObject {
doSomething() {
// Perform the desired action
}
}
function getObject(value) {
if (value) {
return new NonNullObject();
}
return new NullObject();
}
const obj1 = getObject(false);
obj1.doSomething(); // Executes default behavior
const obj2 = getObject(true);
obj2.doSomething(); // Executes desired action
The Builder Pattern separates the construction of complex objects from their representation, providing a step-by-step approach to creating objects. It allows us to construct objects with different configurations or parameters while maintaining a clean and readable codebase.
Code Example:
class Product {
constructor(name, price, description) {
this.name = name;
this.price = price;
this.description = description;
}
}
class ProductBuilder {
constructor() {
this.name = '';
this.price = 0;
this.description = '';
}
setName(name) {
this.name = name;
return this;
}
setPrice(price) {
this.price = price;
return this;
}
setDescription(description) {
this.description = description;
return this;
}
build() {
return new Product(this.name, this.price, this.description);
}
}
const product = new ProductBuilder()
.setName('Example Product')
.setPrice(10.99)
.setDescription('This is an example product.')
.build();
The Singleton Pattern ensures that a class has only one instance throughout the application and provides a global point of access to it. It is commonly used when we need to manage shared resources, configuration settings, or maintain a single state across multiple components.
Code Example:
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
Singleton.instance = this;
// Additional initialization code
}
}
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2);
// true, both instances are the same
The Facade Pattern simplifies complex subsystems by providing a unified interface. It acts as a single entry point, hiding the underlying complexities and providing a simpler API for client interaction.
Code Example:
class Subsystem1 {
operation1() {
// Perform operation 1
}
}
class Subsystem2 {
operation2() {
// Perform operation 2
}
}
class Facade {
constructor() {
this.subsystem1 = new Subsystem1();
this.subsystem2 = new Subsystem2();
}
operate() {
this.subsystem1.operation1();
this.subsystem2.operation2();
}
}
const facade = new Facade();
facade.operate();
// Calls both subsystem operations through the facade
The Command Pattern encapsulates a request as an object, allowing us to parameterize clients with different requests, queue or log requests, and support undoable operations. It decouples the sender of a request from the object that acts.
Code Example:
class Receiver {
executeAction() {
// Perform the desired action
}
}
class Command {
constructor(receiver) {
this.receiver = receiver;
}
execute() {
this.receiver.executeAction();
}
}
class Invoker {
constructor() {
this.commands = [];
}
addCommand(command) {
this.commands.push(command);
}
executeCommands() {
this.commands.forEach((command) => command.execute());
}
}
const receiver = new Receiver();
const command = new Command(receiver);
const invoker = new Invoker();
invoker.addCommand(command);
invoker.executeCommands(); // Executes the action through the command
Design patterns are invaluable tools for developers, enabling us to tackle common design challenges, enhance code structure, and promote code reusability. In this blog post, we explored five powerful design patterns in JavaScript: Null Object Pattern, Builder Pattern, Singleton Pattern, Facade Pattern, and Command Pattern. Each pattern has its unique purpose and benefits, and understanding when and how to apply them can greatly improve the quality and maintainability of your code. By leveraging these design patterns, you'll be better equipped to build robust and scalable applications.
Happy coding! 🚀