rws8.tech
  • Home
  • Tutorials
  • About
Home > Tutorials > JavaScript > Facade Pattern > Lesson 1

Mastering the Facade Pattern

Simplify Complex Interfaces

Learning Objectives

  • Understand the Facade pattern concept
  • Simplify complex APIs
  • Hide implementation details
  • Create unified interfaces
  • Apply facade best practices

What is the Facade Pattern?

A Facade wraps complex subsystems with a simple, unified interface that's easier to use. It hides the complexity and provides a clean API for common tasks.

Basic Example

// Complex subsystem with many classes
class CPU {
    freeze() { 
        console.log('CPU frozen'); 
    }
    
    jump(position) { 
        console.log(`Jumping to position ${position}`); 
    }
    
    execute() { 
        console.log('Executing instructions'); 
    }
}

class Memory {
    load(position, data) { 
        console.log(`Loading ${data} at position ${position}`); 
    }
}

class HardDrive {
    read(sector, size) { 
        console.log(`Reading ${size} bytes from sector ${sector}`);
        return 'boot data';
    }
}

// Facade - provides simple interface
class ComputerFacade {
    constructor() {
        this.cpu = new CPU();
        this.memory = new Memory();
        this.hardDrive = new HardDrive();
    }
    
    start() {
        console.log('Starting computer...');
        this.cpu.freeze();
        const bootData = this.hardDrive.read(0, 1024);
        this.memory.load(0, bootData);
        this.cpu.jump(0);
        this.cpu.execute();
        console.log('Computer started!');
    }
}

// Usage - simple and clean!
const computer = new ComputerFacade();
computer.start(); // One simple method call

Real-World Examples

API Facade

class ApiFacade {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
        this.token = null;
    }
    
    setAuthToken(token) {
        this.token = token;
    }
    
    async request(endpoint, options = {}) {
        const url = `${this.baseUrl}${endpoint}`;
        const headers = {
            'Content-Type': 'application/json',
            ...options.headers
        };
        
        if (this.token) {
            headers['Authorization'] = `Bearer ${this.token}`;
        }
        
        const response = await fetch(url, {
            ...options,
            headers
        });
        
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        
        return response.json();
    }
    
    async getUser(id) {
        return this.request(`/users/${id}`);
    }
    
    async createUser(userData) {
        return this.request('/users', {
            method: 'POST',
            body: JSON.stringify(userData)
        });
    }
    
    async updateUser(id, userData) {
        return this.request(`/users/${id}`, {
            method: 'PUT',
            body: JSON.stringify(userData)
        });
    }
    
    async deleteUser(id) {
        return this.request(`/users/${id}`, {
            method: 'DELETE'
        });
    }
}

// Usage - simple and consistent
const api = new ApiFacade('https://api.example.com');
api.setAuthToken('my-token');

const user = await api.getUser(1);
const newUser = await api.createUser({ name: 'John' });

Browser Storage Facade

class StorageFacade {
    set(key, value, expiresIn = null) {
        const item = {
            value,
            timestamp: Date.now(),
            expiresIn
        };
        
        try {
            localStorage.setItem(key, JSON.stringify(item));
            return true;
        } catch (error) {
            console.error('Storage error:', error);
            return false;
        }
    }
    
    get(key) {
        try {
            const item = JSON.parse(localStorage.getItem(key));
            
            if (!item) return null;
            
            if (item.expiresIn) {
                const age = Date.now() - item.timestamp;
                if (age > item.expiresIn) {
                    this.remove(key);
                    return null;
                }
            }
            
            return item.value;
        } catch (error) {
            console.error('Storage error:', error);
            return null;
        }
    }
    
    remove(key) {
        localStorage.removeItem(key);
    }
    
    clear() {
        localStorage.clear();
    }
    
    has(key) {
        return this.get(key) !== null;
    }
}

// Usage - simple interface
const storage = new StorageFacade();

storage.set('user', { name: 'John' }, 3600000); // 1 hour expiry
const user = storage.get('user');
storage.remove('user');

Event Handler Facade

class EventFacade {
    constructor(element) {
        this.element = element;
        this.listeners = new Map();
    }
    
    on(event, handler) {
        if (!this.listeners.has(event)) {
            this.listeners.set(event, []);
        }
        
        this.listeners.get(event).push(handler);
        this.element.addEventListener(event, handler);
        
        return () => this.off(event, handler);
    }
    
    off(event, handler) {
        const handlers = this.listeners.get(event);
        if (handlers) {
            const index = handlers.indexOf(handler);
            if (index > -1) {
                handlers.splice(index, 1);
            }
        }
        
        this.element.removeEventListener(event, handler);
    }
    
    once(event, handler) {
        const onceHandler = (e) => {
            handler(e);
            this.off(event, onceHandler);
        };
        
        this.on(event, onceHandler);
    }
    
    emit(event, data) {
        const customEvent = new CustomEvent(event, { detail: data });
        this.element.dispatchEvent(customEvent);
    }
    
    removeAll(event) {
        const handlers = this.listeners.get(event);
        if (handlers) {
            handlers.forEach(handler => {
                this.element.removeEventListener(event, handler);
            });
            this.listeners.delete(event);
        }
    }
}

// Usage
const button = document.querySelector('#myButton');
const events = new EventFacade(button);

const unsubscribe = events.on('click', (e) => {
    console.log('Clicked!');
});

events.once('click', () => {
    console.log('First click only');
});

unsubscribe(); // Easy cleanup

When to Use Facade Pattern

Good use cases:
  • Simplify complex APIs or libraries
  • Hide implementation details
  • Provide unified interface to subsystems
  • Reduce dependencies on internal workings
  • Improve testability by isolating complexity
  • Create convenience methods for common tasks

Benefits

  • Simplicity: Easy-to-use interface
  • Loose coupling: Clients don't depend on subsystem details
  • Easy to use: Common tasks become simple
  • Hides complexity: Internal details are hidden
  • Flexibility: Can change subsystem without affecting clients

Best Practices

1. Keep facade methods simple
// Good - simple, focused methods
class ApiFacade {
    async getUser(id) {
        return this.request(`/users/${id}`);
    }
}
2. Don't hide everything
// Allow access to subsystems if needed
class Facade {
    constructor() {
        this.subsystem = new Subsystem();
    }
    
    // Simple methods for common tasks
    doCommonTask() {
        this.subsystem.complexMethod();
    }
    
    // But allow direct access if needed
    getSubsystem() {
        return this.subsystem;
    }
}
3. Handle errors gracefully
async request(endpoint) {
    try {
        const response = await fetch(endpoint);
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        return response.json();
    } catch (error) {
        console.error('Request failed:', error);
        throw error;
    }
}

Key Takeaways

  • Facade simplifies complex subsystems
  • Provides unified, easy-to-use interface
  • Hides implementation details from clients
  • Makes code easier to use and test
  • Common in library and framework design
  • Reduces coupling between client and subsystem
  • Don't hide everything - allow access when needed
  • Keep facade methods simple and focused
← Back to Overview More JavaScript Tutorials →

© 2024 rws8.tech. All rights reserved.

GitHub LinkedIn