Facade Pattern: Simplifying Complex Subsystems in Java

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

  1. Keep Facades Focused
  • Each facade should have a single responsibility
  • Don't create "god object" facades
  1. 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;
}
}
  1. Don't Add Business Logic
  • Facades should delegate, not implement logic
  • Keep business logic in the subsystem classes
  1. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper