The Facade Pattern is a structural design pattern that provides a simplified interface to a complex subsystem of classes, libraries, or frameworks. It hides the complexities of the underlying system and provides a unified, higher-level interface that makes the subsystem easier to use.
What is the Facade Pattern?
The Facade Pattern creates a simple interface that delegates client requests to appropriate objects within the subsystem. It doesn't add new functionality but simplifies access to existing functionality.
Key Benefits:
- Simplifies complex systems - Provides a clean, simple API
- Decouples clients from subsystems - Reduces dependencies
- Improves readability - Makes code easier to understand
- Promotes loose coupling - Clients depend only on the facade
- Easier to test - Mock the facade instead of entire subsystem
Structure
Client → Facade → Subsystem Classes - ClassA - ClassB - ClassC
Implementation Examples
Example 1: Home Theater System Facade
// Complex subsystem classes class Amplifier { private int volume = 0; private boolean poweredOn = false; public void on() { poweredOn = true; System.out.println("Amplifier powered on"); } public void off() { poweredOn = false; System.out.println("Amplifier powered off"); } public void setVolume(int volume) { this.volume = volume; System.out.println("Amplifier volume set to " + volume); } public void setSurroundSound() { System.out.println("Amplifier set to surround sound mode"); } } class DvdPlayer { private boolean poweredOn = false; private String movie; public void on() { poweredOn = true; System.out.println("DVD Player powered on"); } public void off() { poweredOn = false; System.out.println("DVD Player powered off"); } public void play(String movie) { this.movie = movie; System.out.println("Playing movie: " + movie); } public void stop() { System.out.println("Stopping movie: " + movie); this.movie = null; } public void eject() { System.out.println("Ejecting DVD"); } } class Projector { private boolean poweredOn = false; public void on() { poweredOn = true; System.out.println("Projector powered on"); } public void off() { poweredOn = false; System.out.println("Projector powered off"); } public void setWidescreenMode() { System.out.println("Projector set to widescreen mode"); } } class Lights { public void dim(int level) { System.out.println("Lights dimmed to " + level + "%"); } public void on() { System.out.println("Lights turned on"); } } class Screen { public void down() { System.out.println("Screen lowered"); } public void up() { System.out.println("Screen raised"); } } // Facade class class HomeTheaterFacade { private final Amplifier amplifier; private final DvdPlayer dvdPlayer; private final Projector projector; private final Lights lights; private final Screen screen; public HomeTheaterFacade(Amplifier amplifier, DvdPlayer dvdPlayer, Projector projector, Lights lights, Screen screen) { this.amplifier = amplifier; this.dvdPlayer = dvdPlayer; this.projector = projector; this.lights = lights; this.screen = screen; } // Simplified methods for common operations public void watchMovie(String movie) { System.out.println("\n=== Getting ready to watch a movie ==="); screen.down(); lights.dim(10); projector.on(); projector.setWidescreenMode(); amplifier.on(); amplifier.setSurroundSound(); amplifier.setVolume(5); dvdPlayer.on(); dvdPlayer.play(movie); System.out.println("=== Movie setup complete ===\n"); } public void endMovie() { System.out.println("\n=== Shutting down movie theater ==="); dvdPlayer.stop(); dvdPlayer.eject(); dvdPlayer.off(); amplifier.off(); projector.off(); lights.on(); screen.up(); System.out.println("=== Theater shutdown complete ===\n"); } public void listenToMusic(String album) { System.out.println("\n=== Setting up for music ==="); lights.dim(30); amplifier.on(); amplifier.setVolume(3); // For music, we don't need projector or screen System.out.println("Ready to listen to: " + album); System.out.println("=== Music setup complete ===\n"); } } // Client code public class HomeTheaterDemo { public static void main(String[] args) { // Create subsystem components Amplifier amplifier = new Amplifier(); DvdPlayer dvdPlayer = new DvdPlayer(); Projector projector = new Projector(); Lights lights = new Lights(); Screen screen = new Screen(); // Create facade HomeTheaterFacade homeTheater = new HomeTheaterFacade( amplifier, dvdPlayer, projector, lights, screen); // Use simplified interface homeTheater.watchMovie("The Matrix"); homeTheater.endMovie(); homeTheater.listenToMusic("Dark Side of the Moon"); } } Output:
=== Getting ready to watch a movie === Screen lowered Lights dimmed to 10% Projector powered on Projector set to widescreen mode Amplifier powered on Amplifier set to surround sound mode Amplifier volume set to 5 DVD Player powered on Playing movie: The Matrix === Movie setup complete === === Shutting down movie theater === Stopping movie: The Matrix Ejecting DVD DVD Player powered off Amplifier powered off Projector powered off Lights turned on Screen raised === Theater shutdown complete === === Setting up for music === Lights dimmed to 30% Amplifier powered on Amplifier volume set to 3 Ready to listen to: Dark Side of the Moon === Music setup complete ===
Example 2: Computer System Startup Facade
// Subsystem classes class CPU { public void freeze() { System.out.println("CPU: Freezing processor"); } public void jump(long position) { System.out.println("CPU: Jumping to position " + position); } public void execute() { System.out.println("CPU: Executing instructions"); } } class Memory { public void load(long position, byte[] data) { System.out.println("Memory: Loading data at position " + position); } } class HardDrive { public byte[] read(long lba, int size) { System.out.println("HardDrive: Reading " + size + " bytes from LBA " + lba); return new byte[size]; } } class BIOS { public byte[] loadBIOS() { System.out.println("BIOS: Loading BIOS data"); return new byte[1024]; } public void performPOST() { System.out.println("BIOS: Performing Power-On Self-Test"); } } // Facade class ComputerFacade { private final CPU cpu; private final Memory memory; private final HardDrive hardDrive; private final BIOS bios; private static final long BOOT_ADDRESS = 0x0000; private static final long BOOT_SECTOR = 0x0010; private static final int SECTOR_SIZE = 1024; public ComputerFacade() { this.cpu = new CPU(); this.memory = new Memory(); this.hardDrive = new HardDrive(); this.bios = new BIOS(); } public void start() { System.out.println("=== Computer Starting Up ==="); // Complex startup sequence hidden from client bios.performPOST(); byte[] biosData = bios.loadBIOS(); cpu.freeze(); memory.load(BOOT_ADDRESS, biosData); cpu.jump(BOOT_ADDRESS); cpu.execute(); byte[] bootSector = hardDrive.read(BOOT_SECTOR, SECTOR_SIZE); memory.load(BOOT_ADDRESS, bootSector); cpu.jump(BOOT_ADDRESS); cpu.execute(); System.out.println("=== Computer Ready ===\n"); } public void shutdown() { System.out.println("=== Computer Shutting Down ==="); // Cleanup operations System.out.println("=== Computer Off ===\n"); } } // Client public class ComputerDemo { public static void main(String[] args) { ComputerFacade computer = new ComputerFacade(); // Simple interface for complex operation computer.start(); // Use computer... computer.shutdown(); } } Example 3: E-commerce Order Processing Facade
import java.util.*; // Subsystem classes class InventoryService { public boolean checkAvailability(String productId, int quantity) { System.out.println("Checking availability for product: " + productId + ", quantity: " + quantity); // Simulate inventory check return quantity <= 10; // Assume we have 10 of everything } public void updateInventory(String productId, int quantity) { System.out.println("Updating inventory for product: " + productId + ", change: " + (-quantity)); } } class PaymentService { public boolean processPayment(String orderId, double amount, String paymentMethod) { System.out.println("Processing payment of $" + amount + " via " + paymentMethod + " for order: " + orderId); // Simulate payment processing return amount > 0; } public boolean refundPayment(String orderId, double amount) { System.out.println("Refunding $" + amount + " for order: " + orderId); return true; } } class ShippingService { public String scheduleShipping(String orderId, String address, List<String> products) { String trackingNumber = "TRK" + System.currentTimeMillis(); System.out.println("Scheduled shipping for order: " + orderId + " to: " + address); System.out.println("Tracking number: " + trackingNumber); return trackingNumber; } public void cancelShipping(String trackingNumber) { System.out.println("Cancelled shipping with tracking: " + trackingNumber); } } class NotificationService { public void sendOrderConfirmation(String email, String orderId, double amount) { System.out.println("Sent order confirmation to: " + email + " for order: " + orderId + ", amount: $" + amount); } public void sendShippingNotification(String email, String trackingNumber) { System.out.println("Sent shipping notification to: " + email + " with tracking: " + trackingNumber); } public void sendOrderCancellation(String email, String orderId) { System.out.println("Sent cancellation notification to: " + email + " for order: " + orderId); } } // Data classes class Order { private String orderId; private String customerEmail; private String shippingAddress; private List<OrderItem> items; private double totalAmount; private String paymentMethod; public Order(String orderId, String customerEmail, String shippingAddress, List<OrderItem> items, double totalAmount, String paymentMethod) { this.orderId = orderId; this.customerEmail = customerEmail; this.shippingAddress = shippingAddress; this.items = items; this.totalAmount = totalAmount; this.paymentMethod = paymentMethod; } // Getters public String getOrderId() { return orderId; } public String getCustomerEmail() { return customerEmail; } public String getShippingAddress() { return shippingAddress; } public List<OrderItem> getItems() { return items; } public double getTotalAmount() { return totalAmount; } public String getPaymentMethod() { return paymentMethod; } } class OrderItem { private String productId; private String productName; private int quantity; private double price; public OrderItem(String productId, String productName, int quantity, double price) { this.productId = productId; this.productName = productName; this.quantity = quantity; this.price = price; } // Getters public String getProductId() { return productId; } public String getProductName() { return productName; } public int getQuantity() { return quantity; } public double getPrice() { return price; } } class OrderResult { private final boolean success; private final String orderId; private final String trackingNumber; private final String message; public OrderResult(boolean success, String orderId, String trackingNumber, String message) { this.success = success; this.orderId = orderId; this.trackingNumber = trackingNumber; this.message = message; } // Getters public boolean isSuccess() { return success; } public String getOrderId() { return orderId; } public String getTrackingNumber() { return trackingNumber; } public String getMessage() { return message; } } // Facade class OrderProcessingFacade { private final InventoryService inventoryService; private final PaymentService paymentService; private final ShippingService shippingService; private final NotificationService notificationService; public OrderProcessingFacade() { this.inventoryService = new InventoryService(); this.paymentService = new PaymentService(); this.shippingService = new ShippingService(); this.notificationService = new NotificationService(); } public OrderResult placeOrder(Order order) { System.out.println("\n=== Processing Order: " + order.getOrderId() + " ==="); // 1. Check inventory for (OrderItem item : order.getItems()) { if (!inventoryService.checkAvailability(item.getProductId(), item.getQuantity())) { String message = "Insufficient inventory for product: " + item.getProductName(); System.out.println("❌ " + message); return new OrderResult(false, order.getOrderId(), null, message); } } // 2. Process payment if (!paymentService.processPayment(order.getOrderId(), order.getTotalAmount(), order.getPaymentMethod())) { String message = "Payment processing failed"; System.out.println("❌ " + message); return new OrderResult(false, order.getOrderId(), null, message); } // 3. Update inventory for (OrderItem item : order.getItems()) { inventoryService.updateInventory(item.getProductId(), item.getQuantity()); } // 4. Schedule shipping List<String> productNames = order.getItems().stream() .map(OrderItem::getProductName) .toList(); String trackingNumber = shippingService.scheduleShipping( order.getOrderId(), order.getShippingAddress(), productNames); // 5. Send notifications notificationService.sendOrderConfirmation( order.getCustomerEmail(), order.getOrderId(), order.getTotalAmount()); notificationService.sendShippingNotification( order.getCustomerEmail(), trackingNumber); System.out.println("✅ Order processed successfully!"); return new OrderResult(true, order.getOrderId(), trackingNumber, "Order placed successfully"); } public boolean cancelOrder(Order order, String trackingNumber) { System.out.println("\n=== Cancelling Order: " + order.getOrderId() + " ==="); // 1. Cancel shipping if (trackingNumber != null) { shippingService.cancelShipping(trackingNumber); } // 2. Restore inventory for (OrderItem item : order.getItems()) { inventoryService.updateInventory(item.getProductId(), -item.getQuantity()); } // 3. Process refund boolean refundSuccess = paymentService.refundPayment(order.getOrderId(), order.getTotalAmount()); // 4. Send notification notificationService.sendOrderCancellation(order.getCustomerEmail(), order.getOrderId()); System.out.println("✅ Order cancelled successfully!"); return refundSuccess; } } // Client code public class ECommerceDemo { public static void main(String[] args) { OrderProcessingFacade orderProcessor = new OrderProcessingFacade(); // Create sample order List<OrderItem> items = Arrays.asList( new OrderItem("P001", "Laptop", 1, 999.99), new OrderItem("P002", "Mouse", 2, 25.50) ); Order order = new Order( "ORD-001", "[email protected]", "123 Main St, City, State", items, 1050.99, "Credit Card" ); // Place order using simple facade interface OrderResult result = orderProcessor.placeOrder(order); if (result.isSuccess()) { System.out.println("Order successful! Tracking: " + result.getTrackingNumber()); // Simulate cancellation try { Thread.sleep(1000); orderProcessor.cancelOrder(order, result.getTrackingNumber()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } else { System.out.println("Order failed: " + result.getMessage()); } } } Advanced Facade Pattern
Example 4: Configurable Facade with Multiple Implementations
import java.util.*; // Interface for different facade implementations interface DatabaseFacade { void connect(String connectionString); void disconnect(); void executeQuery(String query); List<Map<String, Object>> executeReadQuery(String query); } // MySQL implementation class MySqlDatabaseFacade implements DatabaseFacade { private MySqlConnection connection; private MySqlQueryExecutor executor; private MySqlResultParser parser; @Override public void connect(String connectionString) { System.out.println("Connecting to MySQL database..."); this.connection = new MySqlConnection(connectionString); this.executor = new MySqlQueryExecutor(connection); this.parser = new MySqlResultParser(); connection.open(); } @Override public void disconnect() { System.out.println("Disconnecting from MySQL database..."); connection.close(); } @Override public void executeQuery(String query) { executor.execute(query); } @Override public List<Map<String, Object>> executeReadQuery(String query) { MySqlResultSet resultSet = executor.executeRead(query); return parser.parse(resultSet); } } // PostgreSQL implementation class PostgreSqlDatabaseFacade implements DatabaseFacade { private PgConnection connection; private PgQueryExecutor executor; private PgResultParser parser; @Override public void connect(String connectionString) { System.out.println("Connecting to PostgreSQL database..."); this.connection = new PgConnection(connectionString); this.executor = new PgQueryExecutor(connection); this.parser = new PgResultParser(); connection.open(); } @Override public void disconnect() { System.out.println("Disconnecting from PostgreSQL database..."); connection.close(); } @Override public void executeQuery(String query) { executor.execute(query); } @Override public List<Map<String, Object>> executeReadQuery(String query) { PgResultSet resultSet = executor.executeRead(query); return parser.parse(resultSet); } } // Subsystem classes (simplified) class MySqlConnection { public MySqlConnection(String connString) { /* ... */ } public void open() { /* ... */ } public void close() { /* ... */ } } class MySqlQueryExecutor { public MySqlQueryExecutor(MySqlConnection conn) { /* ... */ } public void execute(String query) { /* ... */ } public MySqlResultSet executeRead(String query) { /* ... */ return new MySqlResultSet(); } } class MySqlResultParser { public List<Map<String, Object>> parse(MySqlResultSet resultSet) { /* ... */ return new ArrayList<>(); } } class MySqlResultSet { /* ... */ } class PgConnection { public PgConnection(String connString) { /* ... */ } public void open() { /* ... */ } public void close() { /* ... */ } } class PgQueryExecutor { public PgQueryExecutor(PgConnection conn) { /* ... */ } public void execute(String query) { /* ... */ } public PgResultSet executeRead(String query) { /* ... */ return new PgResultSet(); } } class PgResultParser { public List<Map<String, Object>> parse(PgResultSet resultSet) { /* ... */ return new ArrayList<>(); } } class PgResultSet { /* ... */ } // Factory for creating appropriate facade class DatabaseFacadeFactory { public static DatabaseFacade createFacade(DatabaseType type) { return switch (type) { case MYSQL -> new MySqlDatabaseFacade(); case POSTGRESQL -> new PostgreSqlDatabaseFacade(); }; } } enum DatabaseType { MYSQL, POSTGRESQL } // Client public class ConfigurableFacadeDemo { public static void main(String[] args) { // Use MySQL facade DatabaseFacade mysqlFacade = DatabaseFacadeFactory.createFacade(DatabaseType.MYSQL); mysqlFacade.connect("mysql://localhost:3306/mydb"); mysqlFacade.executeQuery("CREATE TABLE users (id INT, name VARCHAR(100))"); List<Map<String, Object>> results = mysqlFacade.executeReadQuery("SELECT * FROM users"); mysqlFacade.disconnect(); // Use PostgreSQL facade DatabaseFacade pgFacade = DatabaseFacadeFactory.createFacade(DatabaseType.POSTGRESQL); pgFacade.connect("postgresql://localhost:5432/mydb"); pgFacade.executeQuery("CREATE TABLE products (id SERIAL, name TEXT)"); List<Map<String, Object>> pgResults = pgFacade.executeReadQuery("SELECT * FROM products"); pgFacade.disconnect(); } } Best Practices
- Keep Facades Focused
- Each facade should have a single responsibility
- Don't create "god object" facades
- Use Dependency Injection
public class OrderProcessingFacade { private final InventoryService inventory; private final PaymentService payment; // Inject dependencies public OrderProcessingFacade(InventoryService inventory, PaymentService payment) { this.inventory = inventory; this.payment = payment; } } - Don't Add Business Logic
- Facades should delegate, not implement logic
- Keep business logic in the subsystem classes
- Use Interfaces for Facades
public interface OrderProcessor { OrderResult placeOrder(Order order); boolean cancelOrder(Order order, String trackingNumber); } public class OrderProcessingFacade implements OrderProcessor { // implementation } When to Use the Facade Pattern
Use the Facade Pattern when:
- You have a complex subsystem that needs a simple interface
- You want to decouple clients from subsystem implementation
- You need to layer your subsystems
- You want to provide a simple interface to a legacy system
Don't use when:
- You need access to all subsystem functionality
- The subsystem is simple enough already
- You're trying to hide necessary complexity
Conclusion
The Facade Pattern is a powerful tool for managing complexity in software systems:
Key Benefits:
- Simplifies Complex Systems - Provides clean, simple APIs
- Reduces Dependencies - Clients depend only on the facade
- Improves Maintainability - Changes to subsystem don't affect clients
- Enhances Testability - Easy to mock the facade
- Promotes Clean Architecture - Clear separation of concerns
Common Use Cases:
- Framework Integration - Simplifying complex frameworks
- Legacy System Wrapping - Modernizing old systems
- API Simplification - Providing clean interfaces to complex APIs
- System Initialization - Coordinating complex startup sequences
By using the Facade Pattern, you can create systems that are easier to understand, maintain, and extend while hiding unnecessary complexity from clients.
Remember: A good facade provides a simplified interface without limiting access to advanced functionality when needed. For advanced use cases, clients can still access subsystem classes directly, but the facade handles the common scenarios.