Imagine you're designing a sophisticated manufacturing system 🏭 for a company that creates custom products:
Factory and Builder patterns work exactly like this manufacturing system. They solve fundamental problems in object creation by providing:
Understanding these creational patterns is essential for building maintainable, flexible applications where object creation needs are complex or likely to change. They form the foundation for dependency injection, plugin architectures, and configuration-driven systems.
Creational design patterns abstract the instantiation process and make systems independent of how objects are created, composed, and represented. They become important when systems need to be flexible about what gets created, who creates it, how it gets created, and when.
Core Problems Creational Patterns Solve:
Theoretical Benefits:
The Factory Pattern implements polymorphism at the creation level - allowing a class to defer instantiation to subclasses or separate factory methods.
Types of Factory Patterns:
Why Factory Patterns Matter:
The Builder Pattern separates the construction of complex objects from their representation so that the same construction process can create different representations.
Builder Pattern Concepts:
When Builder Pattern Excels:
Understanding object lifecycle is crucial for effective creation patterns:
Creation patterns primarily focus on the first three phases, ensuring objects enter the usage phase in a valid, properly configured state.
As applications grow, simple constructors become inadequate for handling complex object creation requirements:
// The problem with simple constructors
class DatabaseConnection {
constructor(host, port, database, username, password, ssl, timeout, retries, poolSize, maxConnections) {
// Too many parameters - error prone and hard to remember
this.host = host;
this.port = port;
this.database = database;
this.username = username;
this.password = password;
this.ssl = ssl;
this.timeout = timeout;
this.retries = retries;
this.poolSize = poolSize;
this.maxConnections = maxConnections;
// Complex validation logic mixed with construction
if (!host || !database) {
throw new Error('Host and database are required');
}
if (port < 1 || port > 65535) {
throw new Error('Port must be between 1 and 65535');
}
// What order do parameters go in?
// Which parameters are optional?
// How do we handle default values?
}
}
// Usage is confusing and error-prone
const db1 = new DatabaseConnection('localhost', 5432, 'mydb', 'user', 'pass', true, 5000, 3, 10, 100);
const db2 = new DatabaseConnection('localhost', 5432, 'mydb', 'user', 'pass', false, null, null, null, null); // Many nulls
// Problems:
// 1. Parameter order is hard to remember
// 2. Many optional parameters lead to null/undefined values
// 3. Validation logic clutters the constructor
// 4. Hard to create variations (development vs production configs)
// 5. No way to validate complex interdependencies
// 6. Constructor does too much (violates SRP)
// Tight coupling example - hard to modify or test
class EmailService {
constructor() {
// Directly coupled to specific implementations
this.transporter = new SMTPTransporter('smtp.gmail.com', 587);
this.templater = new HandlebarsTemplater();
this.logger = new ConsoleLogger();
this.queue = new RedisQueue('localhost', 6379);
}
sendEmail(to, subject, template, data) {
try {
const html = this.templater.compile(template, data);
const message = {
to: to,
subject: subject,
html: html
};
this.queue.add(message);
this.logger.log(`Email queued for ${to}`);
} catch (error) {
this.logger.error(`Failed to send email: ${error.message}`);
}
}
}
// Problems with this approach:
// 1. Can't use different email providers (SendGrid, AWS SES, etc.)
// 2. Can't use different templating engines (Mustache, EJS, etc.)
// 3. Can't use different logging strategies (file, database, etc.)
// 4. Can't use different queuing systems (RabbitMQ, AWS SQS, etc.)
// 5. Hard to test - need real SMTP server, Redis, etc.
// 6. Configuration is hardcoded
// 7. Can't create multiple instances with different configs
// Complex object creation without patterns
function createUserInterface(userType, permissions, theme, language) {
let ui;
// Complex conditional logic for object creation
if (userType === 'admin') {
if (permissions.includes('user-management')) {
ui = new AdminUserManagementUI();
} else if (permissions.includes('content-management')) {
ui = new AdminContentManagementUI();
} else {
ui = new BasicAdminUI();
}
} else if (userType === 'moderator') {
ui = new ModeratorUI();
} else if (userType === 'premium-user') {
ui = new PremiumUserUI();
} else {
ui = new BasicUserUI();
}
// More configuration logic
if (theme === 'dark') {
ui.setDarkTheme();
} else if (theme === 'high-contrast') {
ui.setHighContrastTheme();
}
if (language !== 'en') {
ui.setLanguage(language);
}
// Even more setup...
ui.configure({
animations: userType !== 'basic-user',
notifications: permissions.includes('notifications'),
analytics: permissions.includes('analytics')
});
return ui;
}
// Problems:
// 1. Creation logic is complex and centralized in one function
// 2. Adding new user types requires modifying this function
// 3. Complex conditional logic is hard to test and maintain
// 4. No separation between object creation and configuration
// 5. Difficult to reuse parts of the creation logic
// 6. Hard to extend without breaking existing functionality
The Simple Factory encapsulates object creation in a single method but doesn't use inheritance or polymorphism.
// Simple Factory Pattern implementation
class Logger {
constructor(level = 'info') {
this.level = level;
}
log(level, message) {
const levels = { debug: 0, info: 1, warn: 2, error: 3 };
if (levels[level] >= levels[this.level]) {
this.doLog(level, message);
}
}
doLog(level, message) {
throw new Error('Subclasses must implement doLog method');
}
}
class ConsoleLogger extends Logger {
doLog(level, message) {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}`);
}
}
class FileLogger extends Logger {
constructor(filename, level = 'info') {
super(level);
this.filename = filename;
}
doLog(level, message) {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
// In a real implementation, this would write to a file
console.log(`Writing to ${this.filename}: ${logEntry.trim()}`);
}
}
class DatabaseLogger extends Logger {
constructor(connectionString, level = 'info') {
super(level);
this.connectionString = connectionString;
}
doLog(level, message) {
const timestamp = new Date().toISOString();
// In a real implementation, this would write to a database
console.log(`Saving to database: ${this.connectionString}`, {
timestamp,
level,
message
});
}
}
// Simple Factory for logger creation
class LoggerFactory {
static createLogger(type, options = {}) {
const { level = 'info', ...typeSpecificOptions } = options;
switch (type.toLowerCase()) {
case 'console':
return new ConsoleLogger(level);
case 'file':
if (!typeSpecificOptions.filename) {
throw new Error('File logger requires filename option');
}
return new FileLogger(typeSpecificOptions.filename, level);
case 'database':
if (!typeSpecificOptions.connectionString) {
throw new Error('Database logger requires connectionString option');
}
return new DatabaseLogger(typeSpecificOptions.connectionString, level);
default:
throw new Error(`Unknown logger type: ${type}`);
}
}
// Factory method with validation
static createLoggerWithValidation(type, options = {}) {
// Validate common options
if (options.level && !['debug', 'info', 'warn', 'error'].includes(options.level)) {
throw new Error(`Invalid log level: ${options.level}`);
}
// Use main factory method
return this.createLogger(type, options);
}
// Factory method that supports multiple loggers
static createMultiLogger(configs) {
const loggers = configs.map(config =>
this.createLogger(config.type, config.options)
);
return new MultiLogger(loggers);
}
}
// Multi-logger implementation
class MultiLogger extends Logger {
constructor(loggers) {
super();
this.loggers = loggers;
}
doLog(level, message) {
this.loggers.forEach(logger => {
logger.log(level, message);
});
}
}
// Usage examples
console.log('=== Simple Factory Pattern Demo ===');
// Create different types of loggers
const consoleLogger = LoggerFactory.createLogger('console', { level: 'debug' });
const fileLogger = LoggerFactory.createLogger('file', {
level: 'warn',
filename: 'app.log'
});
const dbLogger = LoggerFactory.createLogger('database', {
level: 'error',
connectionString: 'mongodb://localhost:27017/logs'
});
// Test the loggers
consoleLogger.log('info', 'This is a console log message');
fileLogger.log('warn', 'This is a file log warning');
dbLogger.log('error', 'This is a database log error');
// Create multi-logger
const multiLogger = LoggerFactory.createMultiLogger([
{ type: 'console', options: { level: 'debug' } },
{ type: 'file', options: { level: 'info', filename: 'combined.log' } }
]);
multiLogger.log('info', 'This goes to both console and file');
// Demonstrate validation
try {
LoggerFactory.createLoggerWithValidation('console', { level: 'invalid' });
} catch (error) {
console.log('Validation caught error:', error.message);
}
The Factory Method pattern uses inheritance to let subclasses decide which concrete class to instantiate.
// Factory Method Pattern implementation
// Abstract Creator class
class ConnectionFactory {
// Factory method - subclasses will override this
createConnection(config) {
throw new Error('Subclasses must implement createConnection method');
}
// Template method that uses the factory method
establishConnection(config) {
console.log('Preparing to establish connection...');
// Use factory method to create appropriate connection
const connection = this.createConnection(config);
// Common post-creation logic
this.validateConnection(connection);
this.logConnectionCreated(connection);
return connection;
}
validateConnection(connection) {
if (!connection.connect || typeof connection.connect !== 'function') {
throw new Error('Invalid connection: missing connect method');
}
}
logConnectionCreated(connection) {
console.log(`Created connection of type: ${connection.constructor.name}`);
}
}
// Abstract Connection class
class Connection {
constructor(config) {
this.config = config;
this.isConnected = false;
}
connect() {
throw new Error('Subclasses must implement connect method');
}
disconnect() {
throw new Error('Subclasses must implement disconnect method');
}
query(sql) {
throw new Error('Subclasses must implement query method');
}
}
// Concrete Connection implementations
class MySQLConnection extends Connection {
connect() {
console.log(`Connecting to MySQL at ${this.config.host}:${this.config.port}`);
this.isConnected = true;
return Promise.resolve();
}
disconnect() {
console.log('Disconnecting from MySQL');
this.isConnected = false;
return Promise.resolve();
}
query(sql) {
if (!this.isConnected) {
throw new Error('Not connected to MySQL');
}
console.log(`Executing MySQL query: ${sql}`);
return Promise.resolve([]);
}
}
class PostgreSQLConnection extends Connection {
connect() {
console.log(`Connecting to PostgreSQL at ${this.config.host}:${this.config.port}`);
this.isConnected = true;
return Promise.resolve();
}
disconnect() {
console.log('Disconnecting from PostgreSQL');
this.isConnected = false;
return Promise.resolve();
}
query(sql) {
if (!this.isConnected) {
throw new Error('Not connected to PostgreSQL');
}
console.log(`Executing PostgreSQL query: ${sql}`);
return Promise.resolve([]);
}
}
class MongoDBConnection extends Connection {
connect() {
console.log(`Connecting to MongoDB at ${this.config.host}:${this.config.port}`);
this.isConnected = true;
return Promise.resolve();
}
disconnect() {
console.log('Disconnecting from MongoDB');
this.isConnected = false;
return Promise.resolve();
}
query(operation) {
if (!this.isConnected) {
throw new Error('Not connected to MongoDB');
}
console.log(`Executing MongoDB operation: ${JSON.stringify(operation)}`);
return Promise.resolve([]);
}
}
// Concrete Factory implementations
class MySQLConnectionFactory extends ConnectionFactory {
createConnection(config) {
const mysqlConfig = {
host: config.host || 'localhost',
port: config.port || 3306,
database: config.database,
username: config.username,
password: config.password,
...config
};
return new MySQLConnection(mysqlConfig);
}
}
class PostgreSQLConnectionFactory extends ConnectionFactory {
createConnection(config) {
const pgConfig = {
host: config.host || 'localhost',
port: config.port || 5432,
database: config.database,
username: config.username,
password: config.password,
...config
};
return new PostgreSQLConnection(pgConfig);
}
}
class MongoDBConnectionFactory extends ConnectionFactory {
createConnection(config) {
const mongoConfig = {
host: config.host || 'localhost',
port: config.port || 27017,
database: config.database,
username: config.username,
password: config.password,
...config
};
return new MongoDBConnection(mongoConfig);
}
}
// Usage demonstration
console.log('\n=== Factory Method Pattern Demo ===');
const dbConfig = {
host: 'localhost',
database: 'myapp',
username: 'user',
password: 'password'
};
// Client code doesn't need to know about specific connection types
const mysqlFactory = new MySQLConnectionFactory();
const pgFactory = new PostgreSQLConnectionFactory();
const mongoFactory = new MongoDBConnectionFactory();
// Same interface, different implementations
const mysqlConn = mysqlFactory.establishConnection(dbConfig);
const pgConn = pgFactory.establishConnection(dbConfig);
const mongoConn = mongoFactory.establishConnection(dbConfig);
// All connections have the same interface
mysqlConn.connect().then(() => mysqlConn.query('SELECT * FROM users'));
pgConn.connect().then(() => pgConn.query('SELECT * FROM users'));
mongoConn.connect().then(() => mongoConn.query({ collection: 'users', operation: 'find' }));
The Abstract Factory provides an interface for creating families of related or dependent objects.
// Abstract Factory Pattern implementation
// Abstract UI Component classes
class Button {
constructor(text) {
this.text = text;
}
render() {
throw new Error('Subclasses must implement render method');
}
onClick(handler) {
this.clickHandler = handler;
}
}
class Input {
constructor(placeholder) {
this.placeholder = placeholder;
this.value = '';
}
render() {
throw new Error('Subclasses must implement render method');
}
onInput(handler) {
this.inputHandler = handler;
}
}
class Modal {
constructor(title, content) {
this.title = title;
this.content = content;
this.isVisible = false;
}
render() {
throw new Error('Subclasses must implement render method');
}
show() {
this.isVisible = true;
}
hide() {
this.isVisible = false;
}
}
// Concrete Material Design implementations
class MaterialButton extends Button {
render() {
return `<button class="mdc-button mdc-button--raised">${this.text}</button>`;
}
}
class MaterialInput extends Input {
render() {
return `
<div class="mdc-text-field">
<input class="mdc-text-field__input" placeholder="${this.placeholder}">
<div class="mdc-line-ripple"></div>
</div>
`;
}
}
class MaterialModal extends Modal {
render() {
return `
<div class="mdc-dialog ${this.isVisible ? 'mdc-dialog--open' : ''}">
<div class="mdc-dialog__container">
<div class="mdc-dialog__surface">
<h2 class="mdc-dialog__title">${this.title}</h2>
<div class="mdc-dialog__content">${this.content}</div>
</div>
</div>
</div>
`;
}
}
// Concrete Bootstrap implementations
class BootstrapButton extends Button {
render() {
return `<button class="btn btn-primary">${this.text}</button>`;
}
}
class BootstrapInput extends Input {
render() {
return `<input class="form-control" placeholder="${this.placeholder}">`;
}
}
class BootstrapModal extends Modal {
render() {
return `
<div class="modal ${this.isVisible ? 'show' : ''}" style="display: ${this.isVisible ? 'block' : 'none'}">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${this.title}</h5>
</div>
<div class="modal-body">${this.content}</div>
</div>
</div>
</div>
`;
}
}
// Concrete Ant Design implementations
class AntButton extends Button {
render() {
return `<button class="ant-btn ant-btn-primary">${this.text}</button>`;
}
}
class AntInput extends Input {
render() {
return `<input class="ant-input" placeholder="${this.placeholder}">`;
}
}
class AntModal extends Modal {
render() {
return `
<div class="ant-modal ${this.isVisible ? 'ant-modal-open' : ''}" style="display: ${this.isVisible ? 'block' : 'none'}">
<div class="ant-modal-content">
<div class="ant-modal-header">
<div class="ant-modal-title">${this.title}</div>
</div>
<div class="ant-modal-body">${this.content}</div>
</div>
</div>
`;
}
}
// Abstract Factory interface
class UIComponentFactory {
createButton(text) {
throw new Error('Subclasses must implement createButton method');
}
createInput(placeholder) {
throw new Error('Subclasses must implement createInput method');
}
createModal(title, content) {
throw new Error('Subclasses must implement createModal method');
}
// Template method for creating a complete form
createLoginForm() {
const usernameInput = this.createInput('Username');
const passwordInput = this.createInput('Password');
const loginButton = this.createButton('Login');
const cancelButton = this.createButton('Cancel');
return {
usernameInput,
passwordInput,
loginButton,
cancelButton,
render() {
return `
<form class="login-form">
${usernameInput.render()}
${passwordInput.render()}
<div class="button-group">
${loginButton.render()}
${cancelButton.render()}
</div>
</form>
`;
}
};
}
}
// Concrete Factory implementations
class MaterialUIFactory extends UIComponentFactory {
createButton(text) {
return new MaterialButton(text);
}
createInput(placeholder) {
return new MaterialInput(placeholder);
}
createModal(title, content) {
return new MaterialModal(title, content);
}
}
class BootstrapUIFactory extends UIComponentFactory {
createButton(text) {
return new BootstrapButton(text);
}
createInput(placeholder) {
return new BootstrapInput(placeholder);
}
createModal(title, content) {
return new BootstrapModal(title, content);
}
}
class AntUIFactory extends UIComponentFactory {
createButton(text) {
return new AntButton(text);
}
createInput(placeholder) {
return new AntInput(placeholder);
}
createModal(title, content) {
return new AntModal(title, content);
}
}
// Factory registry for dynamic factory selection
class UIFactoryRegistry {
static factories = {
'material': MaterialUIFactory,
'bootstrap': BootstrapUIFactory,
'antd': AntUIFactory
};
static getFactory(theme) {
const FactoryClass = this.factories[theme];
if (!FactoryClass) {
throw new Error(`Unknown UI theme: ${theme}`);
}
return new FactoryClass();
}
static registerFactory(theme, factoryClass) {
this.factories[theme] = factoryClass;
}
static getAvailableThemes() {
return Object.keys(this.factories);
}
}
// Usage demonstration
console.log('\n=== Abstract Factory Pattern Demo ===');
// Client code that works with any UI framework
function createApplicationUI(theme) {
const factory = UIFactoryRegistry.getFactory(theme);
// Create individual components
const saveButton = factory.createButton('Save');
const searchInput = factory.createInput('Search...');
const helpModal = factory.createModal('Help', 'This is the help content');
// Create complete forms using template method
const loginForm = factory.createLoginForm();
console.log(`\n--- ${theme.toUpperCase()} UI Components ---`);
console.log('Save Button:', saveButton.render());
console.log('Search Input:', searchInput.render());
console.log('Help Modal:', helpModal.render());
console.log('Login Form:', loginForm.render());
return {
saveButton,
searchInput,
helpModal,
loginForm
};
}
// Create UIs with different themes
const materialUI = createApplicationUI('material');
const bootstrapUI = createApplicationUI('bootstrap');
const antdUI = createApplicationUI('antd');
console.log('\nAvailable themes:', UIFactoryRegistry.getAvailableThemes());
// Demonstrate dynamic theme switching
function switchTheme(newTheme) {
console.log(`\nSwitching to ${newTheme} theme...`);
return createApplicationUI(newTheme);
}
const currentUI = switchTheme('bootstrap');
The Builder Pattern constructs complex objects step by step, providing fine control over the construction process.
// Builder Pattern implementation
class DatabaseConfig {
constructor() {
// Private properties - only accessible through builder
this._host = null;
this._port = null;
this._database = null;
this._username = null;
this._password = null;
this._ssl = false;
this._connectionPool = null;
this._timeout = 30000;
this._retries = 3;
this._maxConnections = 10;
// Mark as built to prevent modifications
this._isBuilt = false;
}
// Getters to access the configuration
get host() { return this._host; }
get port() { return this._port; }
get database() { return this._database; }
get username() { return this._username; }
get password() { return this._password; }
get ssl() { return this._ssl; }
get connectionPool() { return this._connectionPool; }
get timeout() { return this._timeout; }
get retries() { return this._retries; }
get maxConnections() { return this._maxConnections; }
// Validation method
validate() {
const errors = [];
if (!this._host) errors.push('Host is required');
if (!this._database) errors.push('Database name is required');
if (!this._username) errors.push('Username is required');
if (!this._password) errors.push('Password is required');
if (this._port && (this._port < 1 || this._port > 65535)) {
errors.push('Port must be between 1 and 65535');
}
if (this._timeout < 1000) errors.push('Timeout must be at least 1000ms');
if (this._retries < 0) errors.push('Retries cannot be negative');
if (this._maxConnections < 1) errors.push('Max connections must be at least 1');
return errors;
}
// Mark as built and freeze the object
_markAsBuilt() {
this._isBuilt = true;
Object.freeze(this);
}
toString() {
return `DatabaseConfig(${this._host}:${this._port}/${this._database})`;
}
}
class DatabaseConfigBuilder {
constructor() {
this.config = new DatabaseConfig();
}
// Required parameters with validation
host(host) {
if (typeof host !== 'string' || host.trim().length === 0) {
throw new Error('Host must be a non-empty string');
}
this.config._host = host.trim();
return this; // Enable method chaining
}
port(port) {
if (typeof port !== 'number' || port < 1 || port > 65535) {
throw new Error('Port must be a number between 1 and 65535');
}
this.config._port = port;
return this;
}
database(database) {
if (typeof database !== 'string' || database.trim().length === 0) {
throw new Error('Database must be a non-empty string');
}
this.config._database = database.trim();
return this;
}
credentials(username, password) {
if (typeof username !== 'string' || username.trim().length === 0) {
throw new Error('Username must be a non-empty string');
}
if (typeof password !== 'string' || password.length === 0) {
throw new Error('Password must be a non-empty string');
}
this.config._username = username.trim();
this.config._password = password;
return this;
}
// Optional parameters with defaults
enableSSL(enabled = true) {
this.config._ssl = Boolean(enabled);
return this;
}
connectionPool(config) {
this.config._connectionPool = {
min: config.min || 1,
max: config.max || this.config._maxConnections,
acquireTimeoutMillis: config.acquireTimeoutMillis || 60000,
createTimeoutMillis: config.createTimeoutMillis || 30000,
destroyTimeoutMillis: config.destroyTimeoutMillis || 5000,
idleTimeoutMillis: config.idleTimeoutMillis || 30000,
reapIntervalMillis: config.reapIntervalMillis || 1000,
createRetryIntervalMillis: config.createRetryIntervalMillis || 200
};
return this;
}
timeout(milliseconds) {
if (typeof milliseconds !== 'number' || milliseconds < 1000) {
throw new Error('Timeout must be a number >= 1000 milliseconds');
}
this.config._timeout = milliseconds;
return this;
}
retries(count) {
if (typeof count !== 'number' || count < 0) {
throw new Error('Retries must be a non-negative number');
}
this.config._retries = count;
return this;
}
maxConnections(count) {
if (typeof count !== 'number' || count < 1) {
throw new Error('Max connections must be a positive number');
}
this.config._maxConnections = count;
return this;
}
// Predefined configurations for common scenarios
forDevelopment() {
return this
.host('localhost')
.port(5432)
.timeout(5000)
.retries(1)
.maxConnections(5)
.enableSSL(false);
}
forProduction() {
return this
.timeout(30000)
.retries(3)
.maxConnections(20)
.enableSSL(true)
.connectionPool({
min: 2,
max: 20,
acquireTimeoutMillis: 60000
});
}
forTesting() {
return this
.host('localhost')
.port(5432)
.database('test_db')
.timeout(1000)
.retries(0)
.maxConnections(1)
.enableSSL(false);
}
// Build method with validation
build() {
// Validate the configuration
const errors = this.config.validate();
if (errors.length > 0) {
throw new Error(`Invalid configuration: ${errors.join(', ')}`);
}
// Mark as built and return immutable object
this.config._markAsBuilt();
return this.config;
}
// Build without validation (for testing)
buildUnsafe() {
this.config._markAsBuilt();
return this.config;
}
}
// Usage examples
console.log('\n=== Builder Pattern Demo ===');
// Basic usage with method chaining
const prodConfig = new DatabaseConfigBuilder()
.host('prod-db.company.com')
.port(5432)
.database('production_app')
.credentials('prod_user', 'secure_password_123')
.forProduction()
.build();
console.log('Production config:', prodConfig.toString());
console.log('SSL enabled:', prodConfig.ssl);
console.log('Connection pool:', prodConfig.connectionPool);
// Development configuration
const devConfig = new DatabaseConfigBuilder()
.database('dev_app')
.credentials('dev_user', 'dev_password')
.forDevelopment()
.build();
console.log('\nDevelopment config:', devConfig.toString());
// Testing configuration
const testConfig = new DatabaseConfigBuilder()
.credentials('test_user', 'test_password')
.forTesting()
.build();
console.log('Test config:', testConfig.toString());
// Custom configuration
const customConfig = new DatabaseConfigBuilder()
.host('custom-host.com')
.port(3306)
.database('custom_db')
.credentials('custom_user', 'custom_pass')
.enableSSL(true)
.timeout(45000)
.retries(5)
.maxConnections(15)
.connectionPool({
min: 3,
max: 15,
acquireTimeoutMillis: 45000
})
.build();
console.log('Custom config:', customConfig.toString());
// Demonstrate validation
try {
const invalidConfig = new DatabaseConfigBuilder()
.host('') // Invalid empty host
.build();
} catch (error) {
console.log('\nValidation error caught:', error.message);
}
// Demonstrate immutability
try {
prodConfig.host = 'hacker-host.com'; // This should fail
} catch (error) {
console.log('Immutability enforced - cannot modify built config');
}
Fluent Builders create APIs that read like natural language, making complex object construction intuitive and expressive.
// Fluent Builder for Query Construction
class QueryBuilder {
constructor() {
this.reset();
}
reset() {
this._select = [];
this._from = null;
this._joins = [];
this._where = [];
this._groupBy = [];
this._having = [];
this._orderBy = [];
this._limit = null;
this._offset = null;
this._parameters = {};
return this;
}
// SELECT clauses
select(...columns) {
this._select.push(...columns);
return this;
}
selectAll() {
this._select = ['*'];
return this;
}
selectCount(column = '*', alias = 'count') {
this._select.push(`COUNT(${column}) AS ${alias}`);
return this;
}
selectSum(column, alias = null) {
const aliasClause = alias ? ` AS ${alias}` : '';
this._select.push(`SUM(${column})${aliasClause}`);
return this;
}
selectDistinct(...columns) {
this._select.push(`DISTINCT ${columns.join(', ')}`);
return this;
}
// FROM clauses
from(table, alias = null) {
this._from = alias ? `${table} AS ${alias}` : table;
return this;
}
// JOIN clauses
join(table, condition, type = 'INNER') {
this._joins.push(`${type} JOIN ${table} ON ${condition}`);
return this;
}
leftJoin(table, condition) {
return this.join(table, condition, 'LEFT');
}
rightJoin(table, condition) {
return this.join(table, condition, 'RIGHT');
}
innerJoin(table, condition) {
return this.join(table, condition, 'INNER');
}
// WHERE clauses
where(condition, parameters = {}) {
this._where.push(condition);
Object.assign(this._parameters, parameters);
return this;
}
whereEquals(column, value) {
const paramName = `param_${Object.keys(this._parameters).length}`;
return this.where(`${column} = :${paramName}`, { [paramName]: value });
}
whereIn(column, values) {
const paramNames = values.map((_, index) => `:in_${index}`);
const params = {};
values.forEach((value, index) => {
params[`in_${index}`] = value;
});
return this.where(`${column} IN (${paramNames.join(', ')})`, params);
}
whereBetween(column, min, max) {
const minParam = `min_${Object.keys(this._parameters).length}`;
const maxParam = `max_${Object.keys(this._parameters).length}`;
return this.where(
`${column} BETWEEN :${minParam} AND :${maxParam}`,
{ [minParam]: min, [maxParam]: max }
);
}
whereLike(column, pattern) {
const paramName = `like_${Object.keys(this._parameters).length}`;
return this.where(`${column} LIKE :${paramName}`, { [paramName]: pattern });
}
whereNull(column) {
return this.where(`${column} IS NULL`);
}
whereNotNull(column) {
return this.where(`${column} IS NOT NULL`);
}
and(condition, parameters = {}) {
if (this._where.length > 0) {
this._where.push(`AND ${condition}`);
} else {
this._where.push(condition);
}
Object.assign(this._parameters, parameters);
return this;
}
or(condition, parameters = {}) {
if (this._where.length > 0) {
this._where.push(`OR ${condition}`);
} else {
this._where.push(condition);
}
Object.assign(this._parameters, parameters);
return this;
}
// GROUP BY and HAVING
groupBy(...columns) {
this._groupBy.push(...columns);
return this;
}
having(condition, parameters = {}) {
this._having.push(condition);
Object.assign(this._parameters, parameters);
return this;
}
// ORDER BY
orderBy(column, direction = 'ASC') {
this._orderBy.push(`${column} ${direction.toUpperCase()}`);
return this;
}
orderByAsc(column) {
return this.orderBy(column, 'ASC');
}
orderByDesc(column) {
return this.orderBy(column, 'DESC');
}
// LIMIT and OFFSET
limit(count) {
this._limit = count;
return this;
}
offset(count) {
this._offset = count;
return this;
}
page(pageNumber, pageSize) {
this._limit = pageSize;
this._offset = (pageNumber - 1) * pageSize;
return this;
}
// Build the SQL query
toSQL() {
const parts = [];
// SELECT
if (this._select.length === 0) {
throw new Error('SELECT clause is required');
}
parts.push(`SELECT ${this._select.join(', ')}`);
// FROM
if (!this._from) {
throw new Error('FROM clause is required');
}
parts.push(`FROM ${this._from}`);
// JOIN
if (this._joins.length > 0) {
parts.push(...this._joins);
}
// WHERE
if (this._where.length > 0) {
parts.push(`WHERE ${this._where.join(' ')}`);
}
// GROUP BY
if (this._groupBy.length > 0) {
parts.push(`GROUP BY ${this._groupBy.join(', ')}`);
}
// HAVING
if (this._having.length > 0) {
parts.push(`HAVING ${this._having.join(' AND ')}`);
}
// ORDER BY
if (this._orderBy.length > 0) {
parts.push(`ORDER BY ${this._orderBy.join(', ')}`);
}
// LIMIT
if (this._limit !== null) {
parts.push(`LIMIT ${this._limit}`);
}
// OFFSET
if (this._offset !== null) {
parts.push(`OFFSET ${this._offset}`);
}
return parts.join(' ');
}
// Get parameters for prepared statements
getParameters() {
return { ...this._parameters };
}
// Execute the query (placeholder - would integrate with actual database)
async execute(database) {
const sql = this.toSQL();
const parameters = this.getParameters();
console.log('Executing SQL:', sql);
console.log('Parameters:', parameters);
// In real implementation, this would execute against actual database
return { sql, parameters, rows: [] };
}
// Create a copy of the builder for reuse
clone() {
const cloned = new QueryBuilder();
cloned._select = [...this._select];
cloned._from = this._from;
cloned._joins = [...this._joins];
cloned._where = [...this._where];
cloned._groupBy = [...this._groupBy];
cloned._having = [...this._having];
cloned._orderBy = [...this._orderBy];
cloned._limit = this._limit;
cloned._offset = this._offset;
cloned._parameters = { ...this._parameters };
return cloned;
}
}
// Usage examples - reads like natural language
console.log('\n=== Fluent Builder Pattern Demo ===');
// Basic query
const simpleQuery = new QueryBuilder()
.select('id', 'name', 'email')
.from('users')
.whereEquals('active', true)
.orderByDesc('created_at')
.limit(10);
console.log('Simple query:');
console.log(simpleQuery.toSQL());
console.log('Parameters:', simpleQuery.getParameters());
// Complex query with joins and conditions
const complexQuery = new QueryBuilder()
.select('u.name', 'u.email', 'p.title AS profile_title', 'COUNT(o.id) AS order_count')
.from('users', 'u')
.leftJoin('profiles p', 'u.id = p.user_id')
.leftJoin('orders o', 'u.id = o.user_id')
.where('u.created_at >= :start_date', { start_date: '2023-01-01' })
.and('u.status = :status', { status: 'active' })
.whereIn('u.role', ['admin', 'moderator', 'user'])
.groupBy('u.id', 'u.name', 'u.email', 'p.title')
.having('COUNT(o.id) > :min_orders', { min_orders: 5 })
.orderByDesc('order_count')
.page(1, 20);
console.log('\nComplex query:');
console.log(complexQuery.toSQL());
console.log('Parameters:', complexQuery.getParameters());
// Reusable base query
const baseUserQuery = new QueryBuilder()
.select('id', 'name', 'email', 'created_at')
.from('users')
.whereEquals('active', true);
// Create variations by cloning
const recentUsers = baseUserQuery.clone()
.where('created_at >= :week_ago', { week_ago: '2023-12-01' })
.orderByDesc('created_at')
.limit(50);
const adminUsers = baseUserQuery.clone()
.whereEquals('role', 'admin')
.orderByAsc('name');
console.log('\nRecent users query:');
console.log(recentUsers.toSQL());
console.log('\nAdmin users query:');
console.log(adminUsers.toSQL());
// Demonstrate search functionality
const searchQuery = new QueryBuilder()
.selectAll()
.from('products')
.whereLike('name', '%laptop%')
.or('description LIKE :desc', { desc: '%computer%' })
.whereBetween('price', 500, 2000)
.whereNotNull('image_url')
.orderByAsc('price')
.limit(25);
console.log('\nSearch query:');
console.log(searchQuery.toSQL());
Factory and Builder patterns taught me that object creation is often as important as the objects themselves. Before understanding these patterns, my constructors were bloated with complex logic and my code was tightly coupled to specific implementations.
The key breakthrough was realizing that good design isn't just about the objects you create - it's about how you create them. These patterns enabled me to build systems that are flexible, extensible, and maintainable.
Builder pattern especially changed how I think about APIs - the fluent interface approach makes complex operations feel natural and self-documenting. It's like providing a domain-specific language for object construction.
Now that you've mastered object creation patterns, we'll explore Strategy Pattern & State Pattern - behavioral patterns that enable flexible algorithms and state-dependent behavior in your applications.
Remember: Creation patterns aren't just about making objects - they're about building flexible, maintainable systems that can evolve with changing requirements! 🚀✨
I'm Rahul, Sr. Software Engineer (SDE II) and passionate content creator. Sharing my expertise in software development to assist learners.
More about me