Facade Pattern - Providing Simplified Interface to Complex Subsystems
Learning Objectives: By the end of this lesson, you will be able to:
- Understand the Facade pattern and identify complex subsystems that need simplification
- Implement facade interfaces that hide complexity from client code
- Apply Facade pattern to create clean APIs and service layers
- Distinguish between Facade, Adapter, and Proxy patterns
- Design facades that promote loose coupling and maintainability
1. Welcome and Introduction (5 minutes)
Icebreaker Activity
Think about starting your car. You simply turn the key or press a button, but behind that simple action, dozens of complex systems activate: fuel injection, ignition, electrical systems, transmission, etc. You don’t need to know about all these subsystems - the car provides a simple “start” interface. This is exactly what the Facade pattern does in software!
Lesson Overview
- Duration: 120 minutes
- Prerequisites: Object-oriented programming, basic understanding of system architecture
- Materials Needed: Development environment with Java, Python, or JavaScript support
2. Learning Objectives and Session Outline (5 minutes)
What We’ll Cover Today:
- Complex Subsystem Problems - When multiple classes create confusing interfaces
- Facade Pattern Structure - Simplified interface design and subsystem coordination
- Implementation Strategies - Single vs multiple facades, stateful vs stateless approaches
- Real-world Applications - API design, service layers, library wrappers
- Design Considerations - When to use facades, avoiding god objects, maintaining flexibility
- Modern Usage - Microservice gateways, framework abstractions, module interfaces
Success Criteria:
By the end of this session, you should be able to design and implement facades that significantly simplify complex system interactions.
3. Foundation Knowledge (15-20 minutes)
Core Concepts
What is the Facade Pattern?
The Facade pattern provides a unified interface to a set of interfaces in a subsystem. It defines a higher-level interface that makes the subsystem easier to use by wrapping a complex subsystem with a simpler interface.
Real-World Analogy: Consider a home theater system. You have a TV, sound system, Blu-ray player, streaming device, lights, and more. Instead of managing each device separately, you could create a universal remote with simple buttons like “Watch Movie” that automatically turns on the TV, dims lights, starts the sound system, and selects the right input.
Why This Matters: Modern software systems often involve multiple complex subsystems. The Facade pattern helps reduce coupling between clients and subsystems, making code easier to understand, maintain, and test.
Problem Without Facade Pattern
// Client code without facade - complex and tightly coupled
class MovieController {
private TV tv;
private SoundSystem soundSystem;
private BluRayPlayer bluRay;
private Lights lights;
private PopcornMaker popcorn;
public void startMovie(String movieTitle) {
// Client must know about all subsystems and their interactions
lights.dim(10);
tv.turnOn();
tv.setInput("HDMI1");
soundSystem.turnOn();
soundSystem.setVolume(70);
soundSystem.setSurroundMode();
bluRay.turnOn();
bluRay.loadMovie(movieTitle);
bluRay.play();
popcorn.turnOn();
popcorn.makePopcorn();
// What if something fails? Client handles all error cases
}
}
Key Terminology
- Facade: The simplified interface that clients interact with
- Subsystem Classes: The complex components that do the actual work
- Client: Code that uses the facade instead of subsystem classes directly
- Delegation: How the facade forwards requests to appropriate subsystem objects
4. Detailed Explanation and Examples (25-30 minutes)
Understanding Facade Pattern Structure
The Facade pattern involves these key components:
- Facade Class: Provides simple methods that delegate to subsystem objects
- Subsystem Classes: Complex components with detailed interfaces
- Client Code: Uses the facade without knowing about subsystem complexity
Practical Applications
Facade pattern is commonly used for:
- API Design: Creating simple interfaces for complex backend services
- Service Layer: Providing business-focused interfaces over data access layers
- Library Wrappers: Simplifying complex third-party libraries
- System Integration: Coordinating multiple systems through unified interfaces
- Legacy System Modernization: Creating modern interfaces for old systems
- Microservice Gateways: Aggregating multiple microservices behind single interfaces
Common Misconceptions
-
Misconception: Facade pattern is the same as Adapter pattern Reality: Facade simplifies interfaces, Adapter makes incompatible interfaces compatible
-
Misconception: Facades should hide all subsystem functionality Reality: Facades provide simplified access; clients can still access subsystems directly if needed
-
Misconception: A facade should be a singleton Reality: Facades can be stateless instances, singletons, or even static methods depending on the use case
5. Live Coding Demonstration (20-25 minutes)
Example 1: File Processing System (Java)
import java.io.*;
import java.util.*;
import java.util.zip.*;
// Complex subsystem classes
class FileReader {
public String readFile(String filePath) throws IOException {
StringBuilder content = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new java.io.FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
}
System.out.println("š File read: " + filePath);
return content.toString();
}
public List<String> readFileLines(String filePath) throws IOException {
List<String> lines = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new java.io.FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
}
return lines;
}
}
class FileWriter {
public void writeFile(String filePath, String content) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new java.io.FileWriter(filePath))) {
writer.write(content);
}
System.out.println("š¾ File written: " + filePath);
}
public void appendToFile(String filePath, String content) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new java.io.FileWriter(filePath, true))) {
writer.write(content);
}
System.out.println("ā Content appended to: " + filePath);
}
}
class FileValidator {
public boolean validateFileExists(String filePath) {
File file = new File(filePath);
boolean exists = file.exists() && file.isFile();
System.out.println("ā
File validation for " + filePath + ": " + exists);
return exists;
}
public boolean validateFileReadable(String filePath) {
File file = new File(filePath);
boolean readable = file.canRead();
System.out.println("š Read permission for " + filePath + ": " + readable);
return readable;
}
public boolean validateFileWritable(String filePath) {
File file = new File(filePath);
boolean writable = file.canWrite();
System.out.println("āļø Write permission for " + filePath + ": " + writable);
return writable;
}
}
class FileCompressor {
public void compressFile(String inputPath, String outputPath) throws IOException {
try (FileInputStream fis = new FileInputStream(inputPath);
FileOutputStream fos = new FileOutputStream(outputPath);
GZIPOutputStream gzos = new GZIPOutputStream(fos)) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) > 0) {
gzos.write(buffer, 0, len);
}
}
System.out.println("šļø File compressed: " + inputPath + " -> " + outputPath);
}
public void decompressFile(String inputPath, String outputPath) throws IOException {
try (FileInputStream fis = new FileInputStream(inputPath);
GZIPInputStream gzis = new GZIPInputStream(fis);
FileOutputStream fos = new FileOutputStream(outputPath)) {
byte[] buffer = new byte[1024];
int len;
while ((len = gzis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}
System.out.println("š File decompressed: " + inputPath + " -> " + outputPath);
}
}
class FileFormatter {
public String formatAsJson(List<String> lines) {
StringBuilder json = new StringBuilder("{\n \"lines\": [\n");
for (int i = 0; i < lines.size(); i++) {
json.append(" \"").append(lines.get(i).replace("\"", "\\\"")).append("\"");
if (i < lines.size() - 1) json.append(",");
json.append("\n");
}
json.append(" ]\n}");
return json.toString();
}
public String formatAsXml(List<String> lines) {
StringBuilder xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document>\n");
for (int i = 0; i < lines.size(); i++) {
xml.append(" <line id=\"").append(i + 1).append("\">")
.append(lines.get(i).replace("&", "&").replace("<", "<").replace(">", ">"))
.append("</line>\n");
}
xml.append("</document>");
return xml.toString();
}
public String formatAsCsv(List<String> lines) {
StringBuilder csv = new StringBuilder("line_number,content\n");
for (int i = 0; i < lines.size(); i++) {
csv.append(i + 1).append(",\"")
.append(lines.get(i).replace("\"", "\"\""))
.append("\"\n");
}
return csv.toString();
}
}
// Facade that simplifies file operations
class FileProcessingFacade {
private FileReader fileReader;
private FileWriter fileWriter;
private FileValidator fileValidator;
private FileCompressor fileCompressor;
private FileFormatter fileFormatter;
public FileProcessingFacade() {
this.fileReader = new FileReader();
this.fileWriter = new FileWriter();
this.fileValidator = new FileValidator();
this.fileCompressor = new FileCompressor();
this.fileFormatter = new FileFormatter();
}
// Simple method that coordinates multiple subsystems
public boolean processAndBackupFile(String inputPath, String outputPath, String format) {
try {
System.out.println("š Starting file processing: " + inputPath);
// Validate input file
if (!fileValidator.validateFileExists(inputPath) ||
!fileValidator.validateFileReadable(inputPath)) {
System.out.println("ā Input file validation failed");
return false;
}
// Read file content
List<String> lines = fileReader.readFileLines(inputPath);
if (lines.isEmpty()) {
System.out.println("ā ļø File is empty");
return false;
}
// Format content based on requested format
String formattedContent;
switch (format.toLowerCase()) {
case "json":
formattedContent = fileFormatter.formatAsJson(lines);
break;
case "xml":
formattedContent = fileFormatter.formatAsXml(lines);
break;
case "csv":
formattedContent = fileFormatter.formatAsCsv(lines);
break;
default:
formattedContent = String.join("\n", lines);
break;
}
// Write formatted content
fileWriter.writeFile(outputPath, formattedContent);
// Create compressed backup
String backupPath = outputPath + ".gz";
fileCompressor.compressFile(outputPath, backupPath);
System.out.println("ā
File processing completed successfully");
return true;
} catch (IOException e) {
System.out.println("ā File processing failed: " + e.getMessage());
return false;
}
}
// Simplified method for common operation
public boolean copyAndCompressFile(String sourcePath, String destinationPath) {
try {
if (!fileValidator.validateFileExists(sourcePath)) {
return false;
}
String content = fileReader.readFile(sourcePath);
fileWriter.writeFile(destinationPath, content);
fileCompressor.compressFile(destinationPath, destinationPath + ".gz");
return true;
} catch (IOException e) {
System.out.println("ā Copy and compress failed: " + e.getMessage());
return false;
}
}
// Method that provides safe file conversion
public boolean convertFileFormat(String inputPath, String outputPath, String targetFormat) {
try {
if (!fileValidator.validateFileExists(inputPath)) {
return false;
}
List<String> lines = fileReader.readFileLines(inputPath);
String convertedContent;
switch (targetFormat.toLowerCase()) {
case "json":
convertedContent = fileFormatter.formatAsJson(lines);
outputPath = outputPath.endsWith(".json") ? outputPath : outputPath + ".json";
break;
case "xml":
convertedContent = fileFormatter.formatAsXml(lines);
outputPath = outputPath.endsWith(".xml") ? outputPath : outputPath + ".xml";
break;
case "csv":
convertedContent = fileFormatter.formatAsCsv(lines);
outputPath = outputPath.endsWith(".csv") ? outputPath : outputPath + ".csv";
break;
default:
System.out.println("ā Unsupported format: " + targetFormat);
return false;
}
fileWriter.writeFile(outputPath, convertedContent);
System.out.println("š File converted to " + targetFormat + ": " + outputPath);
return true;
} catch (IOException e) {
System.out.println("ā File conversion failed: " + e.getMessage());
return false;
}
}
// Batch operation that would be complex without facade
public int processBatchFiles(List<String> inputPaths, String outputDirectory, String format) {
int successCount = 0;
for (String inputPath : inputPaths) {
File inputFile = new File(inputPath);
String fileName = inputFile.getName();
String outputPath = outputDirectory + "/" + fileName + "_processed";
if (processAndBackupFile(inputPath, outputPath, format)) {
successCount++;
}
}
System.out.println("š Batch processing completed: " + successCount + "/" + inputPaths.size() + " files processed");
return successCount;
}
}
// Client code using the facade
class FileProcessingClient {
public static void main(String[] args) {
FileProcessingFacade facade = new FileProcessingFacade();
// Simple file processing - client doesn't need to know about subsystems
System.out.println("=== Simple File Processing ===");
facade.processAndBackupFile("/tmp/input.txt", "/tmp/output.json", "json");
System.out.println("\n=== File Conversion ===");
facade.convertFileFormat("/tmp/input.txt", "/tmp/data", "csv");
System.out.println("\n=== Copy and Compress ===");
facade.copyAndCompressFile("/tmp/input.txt", "/tmp/copy.txt");
System.out.println("\n=== Batch Processing ===");
List<String> files = Arrays.asList("/tmp/file1.txt", "/tmp/file2.txt", "/tmp/file3.txt");
facade.processBatchFiles(files, "/tmp/processed", "xml");
}
}
Explanation:
- The facade coordinates multiple subsystems (FileReader, FileWriter, FileValidator, etc.)
- Client code uses simple methods without knowing about subsystem complexity
- Error handling and validation are centralized in the facade
- Complex workflows are broken down into simple method calls
Example 2: E-commerce Order Processing System (Python)
from abc import ABC, abstractmethod
from typing import List, Dict, Optional, Tuple
from datetime import datetime, timedelta
from decimal import Decimal
import json
# Complex subsystem classes
class InventoryService:
def __init__(self):
self.inventory = {
"LAPTOP001": {"quantity": 50, "price": 999.99, "reserved": 0},
"MOUSE001": {"quantity": 100, "price": 29.99, "reserved": 0},
"KEYBOARD001": {"quantity": 75, "price": 79.99, "reserved": 0},
"MONITOR001": {"quantity": 30, "price": 299.99, "reserved": 0}
}
def check_availability(self, product_id: str, quantity: int) -> bool:
if product_id not in self.inventory:
return False
available = self.inventory[product_id]["quantity"] - self.inventory[product_id]["reserved"]
result = available >= quantity
print(f"š¦ Inventory check for {product_id} (qty: {quantity}): {'ā
Available' if result else 'ā Not available'}")
return result
def reserve_items(self, product_id: str, quantity: int) -> bool:
if self.check_availability(product_id, quantity):
self.inventory[product_id]["reserved"] += quantity
print(f"š Reserved {quantity} units of {product_id}")
return True
return False
def release_reservation(self, product_id: str, quantity: int):
if product_id in self.inventory:
self.inventory[product_id]["reserved"] = max(0,
self.inventory[product_id]["reserved"] - quantity)
print(f"š Released reservation for {quantity} units of {product_id}")
def commit_reservation(self, product_id: str, quantity: int):
if product_id in self.inventory:
self.inventory[product_id]["quantity"] -= quantity
self.inventory[product_id]["reserved"] = max(0,
self.inventory[product_id]["reserved"] - quantity)
print(f"ā
Committed sale of {quantity} units of {product_id}")
def get_price(self, product_id: str) -> Optional[Decimal]:
if product_id in self.inventory:
return Decimal(str(self.inventory[product_id]["price"]))
return None
class PaymentService:
def __init__(self):
self.transactions = {}
self.transaction_counter = 1000
def validate_payment_info(self, payment_info: Dict) -> bool:
required_fields = ["card_number", "expiry", "cvv", "cardholder_name"]
valid = all(field in payment_info and payment_info[field] for field in required_fields)
print(f"š³ Payment validation: {'ā
Valid' if valid else 'ā Invalid'}")
return valid
def authorize_payment(self, amount: Decimal, payment_info: Dict) -> Optional[str]:
if not self.validate_payment_info(payment_info):
return None
# Simulate payment authorization
if amount <= 0:
print("ā Payment authorization failed: Invalid amount")
return None
transaction_id = f"TXN_{self.transaction_counter}"
self.transaction_counter += 1
self.transactions[transaction_id] = {
"amount": amount,
"status": "authorized",
"timestamp": datetime.now(),
"payment_info": payment_info
}
print(f"ā
Payment authorized: {transaction_id} for ${amount}")
return transaction_id
def capture_payment(self, transaction_id: str) -> bool:
if transaction_id in self.transactions:
self.transactions[transaction_id]["status"] = "captured"
amount = self.transactions[transaction_id]["amount"]
print(f"š° Payment captured: {transaction_id} for ${amount}")
return True
print(f"ā Payment capture failed: Transaction {transaction_id} not found")
return False
def void_payment(self, transaction_id: str) -> bool:
if transaction_id in self.transactions:
self.transactions[transaction_id]["status"] = "voided"
amount = self.transactions[transaction_id]["amount"]
print(f"š« Payment voided: {transaction_id} for ${amount}")
return True
return False
class ShippingService:
def __init__(self):
self.shipping_rates = {
"standard": {"cost": 5.99, "days": 5},
"express": {"cost": 15.99, "days": 2},
"overnight": {"cost": 29.99, "days": 1}
}
self.tracking_counter = 5000
def calculate_shipping_cost(self, items: List[Dict], shipping_method: str) -> Decimal:
if shipping_method not in self.shipping_rates:
return Decimal("0")
base_cost = Decimal(str(self.shipping_rates[shipping_method]["cost"]))
# Add weight-based calculation (simplified)
weight_cost = Decimal(str(len(items) * 2.50))
total_cost = base_cost + weight_cost
print(f"š Shipping cost calculated: ${total_cost} ({shipping_method})")
return total_cost
def create_shipping_label(self, order_info: Dict, shipping_method: str) -> Optional[str]:
tracking_number = f"TRACK_{self.tracking_counter}"
self.tracking_counter += 1
delivery_date = datetime.now() + timedelta(days=self.shipping_rates[shipping_method]["days"])
print(f"š Shipping label created: {tracking_number}")
print(f" š
Estimated delivery: {delivery_date.strftime('%Y-%m-%d')}")
return tracking_number
def schedule_pickup(self, tracking_number: str) -> bool:
pickup_date = datetime.now() + timedelta(days=1)
print(f"š Pickup scheduled for {tracking_number} on {pickup_date.strftime('%Y-%m-%d')}")
return True
class NotificationService:
def send_order_confirmation(self, customer_email: str, order_id: str, order_details: Dict):
print(f"š§ Order confirmation sent to {customer_email}")
print(f" Order ID: {order_id}")
print(f" Total: ${order_details.get('total', 0)}")
def send_shipping_notification(self, customer_email: str, tracking_number: str):
print(f"š§ Shipping notification sent to {customer_email}")
print(f" Tracking number: {tracking_number}")
def send_payment_failure_notification(self, customer_email: str, order_id: str):
print(f"š§ Payment failure notification sent to {customer_email} for order {order_id}")
class OrderService:
def __init__(self):
self.orders = {}
self.order_counter = 10000
def create_order(self, customer_info: Dict, items: List[Dict]) -> str:
order_id = f"ORD_{self.order_counter}"
self.order_counter += 1
self.orders[order_id] = {
"id": order_id,
"customer": customer_info,
"items": items,
"status": "created",
"created_at": datetime.now(),
"total": Decimal("0")
}
print(f"š Order created: {order_id}")
return order_id
def update_order_status(self, order_id: str, status: str):
if order_id in self.orders:
self.orders[order_id]["status"] = status
print(f"š Order {order_id} status updated to: {status}")
def update_order_total(self, order_id: str, total: Decimal):
if order_id in self.orders:
self.orders[order_id]["total"] = total
def get_order(self, order_id: str) -> Optional[Dict]:
return self.orders.get(order_id)
# Facade that simplifies the complex e-commerce process
class EcommerceFacade:
def __init__(self):
self.inventory_service = InventoryService()
self.payment_service = PaymentService()
self.shipping_service = ShippingService()
self.notification_service = NotificationService()
self.order_service = OrderService()
def process_order(self, customer_info: Dict, items: List[Dict],
payment_info: Dict, shipping_method: str = "standard") -> Dict:
"""
Main facade method that coordinates the entire order process
"""
print("š Starting order processing...")
try:
# Step 1: Create order
order_id = self.order_service.create_order(customer_info, items)
# Step 2: Check inventory and reserve items
reservations = []
total_amount = Decimal("0")
for item in items:
product_id = item["product_id"]
quantity = item["quantity"]
# Check availability and get price
if not self.inventory_service.check_availability(product_id, quantity):
# Release any previous reservations
self._release_all_reservations(reservations)
self.order_service.update_order_status(order_id, "cancelled_no_inventory")
return {"success": False, "order_id": order_id, "error": f"Insufficient inventory for {product_id}"}
# Reserve items
if self.inventory_service.reserve_items(product_id, quantity):
reservations.append({"product_id": product_id, "quantity": quantity})
# Calculate item total
price = self.inventory_service.get_price(product_id)
if price:
total_amount += price * quantity
# Step 3: Calculate shipping
shipping_cost = self.shipping_service.calculate_shipping_cost(items, shipping_method)
total_amount += shipping_cost
self.order_service.update_order_total(order_id, total_amount)
# Step 4: Process payment
transaction_id = self.payment_service.authorize_payment(total_amount, payment_info)
if not transaction_id:
self._release_all_reservations(reservations)
self.order_service.update_order_status(order_id, "cancelled_payment_failed")
self.notification_service.send_payment_failure_notification(
customer_info["email"], order_id)
return {"success": False, "order_id": order_id, "error": "Payment authorization failed"}
# Step 5: Create shipping label
order_details = self.order_service.get_order(order_id)
tracking_number = self.shipping_service.create_shipping_label(
{"order_id": order_id, "customer": customer_info}, shipping_method)
if not tracking_number:
self.payment_service.void_payment(transaction_id)
self._release_all_reservations(reservations)
self.order_service.update_order_status(order_id, "cancelled_shipping_failed")
return {"success": False, "order_id": order_id, "error": "Shipping label creation failed"}
# Step 6: Finalize the order
# Capture payment
if not self.payment_service.capture_payment(transaction_id):
self._release_all_reservations(reservations)
self.order_service.update_order_status(order_id, "cancelled_payment_capture_failed")
return {"success": False, "order_id": order_id, "error": "Payment capture failed"}
# Commit inventory reservations
for reservation in reservations:
self.inventory_service.commit_reservation(
reservation["product_id"], reservation["quantity"])
# Schedule pickup
self.shipping_service.schedule_pickup(tracking_number)
# Update order status
self.order_service.update_order_status(order_id, "confirmed")
# Send notifications
self.notification_service.send_order_confirmation(
customer_info["email"], order_id, {
"total": total_amount,
"items": items,
"tracking_number": tracking_number
})
self.notification_service.send_shipping_notification(
customer_info["email"], tracking_number)
print("ā
Order processing completed successfully!")
return {
"success": True,
"order_id": order_id,
"transaction_id": transaction_id,
"tracking_number": tracking_number,
"total": total_amount,
"estimated_delivery": (datetime.now() +
timedelta(days=self.shipping_service.shipping_rates[shipping_method]["days"])).strftime('%Y-%m-%d')
}
except Exception as e:
# Cleanup on any error
if 'reservations' in locals():
self._release_all_reservations(reservations)
if 'transaction_id' in locals() and transaction_id:
self.payment_service.void_payment(transaction_id)
self.order_service.update_order_status(order_id, "cancelled_error")
print(f"ā Order processing failed: {str(e)}")
return {"success": False, "order_id": order_id, "error": str(e)}
def _release_all_reservations(self, reservations: List[Dict]):
"""Helper method to release all inventory reservations"""
for reservation in reservations:
self.inventory_service.release_reservation(
reservation["product_id"], reservation["quantity"])
def quick_buy(self, customer_email: str, product_id: str, quantity: int = 1) -> Dict:
"""Simplified method for single-item purchases"""
customer_info = {"email": customer_email, "name": "Quick Buy Customer"}
items = [{"product_id": product_id, "quantity": quantity}]
# Use dummy payment info for demonstration
payment_info = {
"card_number": "4111111111111111",
"expiry": "12/25",
"cvv": "123",
"cardholder_name": "Quick Buy Customer"
}
return self.process_order(customer_info, items, payment_info, "standard")
def check_order_status(self, order_id: str) -> Dict:
"""Simple method to check order status"""
order = self.order_service.get_order(order_id)
if order:
return {
"order_id": order_id,
"status": order["status"],
"total": str(order["total"]),
"created_at": order["created_at"].strftime('%Y-%m-%d %H:%M:%S')
}
return {"error": "Order not found"}
# Client code using the facade
def ecommerce_demo():
print("šļø E-commerce Order Processing System Demo\n")
facade = EcommerceFacade()
# Complex order with multiple items
customer_info = {
"name": "John Doe",
"email": "john.doe@example.com",
"address": "123 Main St, City, State, 12345"
}
items = [
{"product_id": "LAPTOP001", "quantity": 1},
{"product_id": "MOUSE001", "quantity": 2},
{"product_id": "KEYBOARD001", "quantity": 1}
]
payment_info = {
"card_number": "4111111111111111",
"expiry": "12/25",
"cvv": "123",
"cardholder_name": "John Doe"
}
print("=== Processing Complex Order ===")
result = facade.process_order(customer_info, items, payment_info, "express")
print(f"Result: {json.dumps(result, indent=2, default=str)}")
if result["success"]:
print(f"\n=== Checking Order Status ===")
status = facade.check_order_status(result["order_id"])
print(f"Order Status: {json.dumps(status, indent=2)}")
print(f"\n=== Quick Buy Demo ===")
quick_result = facade.quick_buy("jane@example.com", "MONITOR001", 1)
print(f"Quick Buy Result: {json.dumps(quick_result, indent=2, default=str)}")
if __name__ == "__main__":
ecommerce_demo()
Example 3: Social Media Integration Facade (JavaScript)
// Complex subsystem classes for different social media APIs
class FacebookAPI {
constructor(accessToken) {
this.accessToken = accessToken;
this.baseUrl = 'https://graph.facebook.com/v12.0';
}
async postToFeed(message, imageUrl = null) {
// Simulate Facebook API call
console.log('š Posting to Facebook...');
const postData = {
message: message,
access_token: this.accessToken
};
if (imageUrl) {
postData.picture = imageUrl;
}
// Simulate API response
await this.delay(500);
const postId = `fb_${Date.now()}`;
console.log(`ā
Facebook post created: ${postId}`);
return { platform: 'facebook', postId, url: `https://facebook.com/posts/${postId}` };
}
async getUserProfile() {
console.log('š¤ Fetching Facebook profile...');
await this.delay(300);
return {
id: 'fb_user_123',
name: 'John Doe',
followers: 1250,
platform: 'facebook'
};
}
async getAnalytics(postId) {
console.log(`š Fetching Facebook analytics for ${postId}...`);
await this.delay(400);
return {
platform: 'facebook',
postId,
likes: Math.floor(Math.random() * 100),
shares: Math.floor(Math.random() * 50),
comments: Math.floor(Math.random() * 25)
};
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
class TwitterAPI {
constructor(apiKey, apiSecret, accessToken, accessTokenSecret) {
this.apiKey = apiKey;
this.apiSecret = apiSecret;
this.accessToken = accessToken;
this.accessTokenSecret = accessTokenSecret;
}
async createTweet(text, mediaIds = []) {
console.log('š¦ Creating tweet...');
if (text.length > 280) {
throw new Error('Tweet exceeds 280 characters');
}
await this.delay(600);
const tweetId = `tw_${Date.now()}`;
console.log(`ā
Tweet created: ${tweetId}`);
return {
platform: 'twitter',
tweetId,
url: `https://twitter.com/user/status/${tweetId}`,
text: text.substring(0, 50) + (text.length > 50 ? '...' : '')
};
}
async uploadMedia(imageUrl) {
console.log('š¼ļø Uploading media to Twitter...');
await this.delay(800);
const mediaId = `media_${Date.now()}`;
console.log(`ā
Media uploaded: ${mediaId}`);
return mediaId;
}
async getUserInfo() {
console.log('š¤ Fetching Twitter profile...');
await this.delay(400);
return {
id: 'tw_user_456',
username: '@johndoe',
followers: 850,
platform: 'twitter'
};
}
async getTweetMetrics(tweetId) {
console.log(`š Fetching Twitter metrics for ${tweetId}...`);
await this.delay(500);
return {
platform: 'twitter',
tweetId,
retweets: Math.floor(Math.random() * 30),
likes: Math.floor(Math.random() * 80),
replies: Math.floor(Math.random() * 15)
};
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
class LinkedInAPI {
constructor(accessToken) {
this.accessToken = accessToken;
}
async sharePost(content, articleUrl = null) {
console.log('š¼ Sharing on LinkedIn...');
const postData = {
content: content,
visibility: 'PUBLIC'
};
if (articleUrl) {
postData.article = articleUrl;
}
await this.delay(700);
const shareId = `li_${Date.now()}`;
console.log(`ā
LinkedIn post shared: ${shareId}`);
return {
platform: 'linkedin',
shareId,
url: `https://linkedin.com/posts/${shareId}`
};
}
async getProfile() {
console.log('š¤ Fetching LinkedIn profile...');
await this.delay(450);
return {
id: 'li_user_789',
name: 'John Doe',
connections: 2300,
platform: 'linkedin'
};
}
async getPostStats(shareId) {
console.log(`š Fetching LinkedIn stats for ${shareId}...`);
await this.delay(550);
return {
platform: 'linkedin',
shareId,
views: Math.floor(Math.random() * 500),
likes: Math.floor(Math.random() * 60),
comments: Math.floor(Math.random() * 20)
};
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
class InstagramAPI {
constructor(accessToken) {
this.accessToken = accessToken;
}
async createPost(imageUrl, caption) {
console.log('šø Creating Instagram post...');
if (!imageUrl) {
throw new Error('Instagram requires an image');
}
await this.delay(900);
const postId = `ig_${Date.now()}`;
console.log(`ā
Instagram post created: ${postId}`);
return {
platform: 'instagram',
postId,
url: `https://instagram.com/p/${postId}`
};
}
async getUserData() {
console.log('š¤ Fetching Instagram profile...');
await this.delay(350);
return {
id: 'ig_user_101',
username: '@johndoe_photo',
followers: 3400,
platform: 'instagram'
};
}
async getInsights(postId) {
console.log(`š Fetching Instagram insights for ${postId}...`);
await this.delay(600);
return {
platform: 'instagram',
postId,
likes: Math.floor(Math.random() * 200),
comments: Math.floor(Math.random() * 40),
saves: Math.floor(Math.random() * 30)
};
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Content formatter utility
class ContentFormatter {
static formatForPlatform(content, platform, maxLength) {
if (content.length <= maxLength) return content;
switch (platform) {
case 'twitter':
return content.substring(0, 276) + '...';
case 'facebook':
return content.substring(0, 2000) + '...';
case 'linkedin':
return content.substring(0, 1300) + '...';
case 'instagram':
return content.substring(0, 2200) + '...';
default:
return content.substring(0, maxLength) + '...';
}
}
static addHashtags(content, hashtags) {
if (!hashtags || hashtags.length === 0) return content;
return content + '\n\n' + hashtags.map(tag => `#${tag}`).join(' ');
}
}
// Facade that simplifies social media operations
class SocialMediaFacade {
constructor(credentials) {
this.facebookAPI = new FacebookAPI(credentials.facebook?.accessToken);
this.twitterAPI = new TwitterAPI(
credentials.twitter?.apiKey,
credentials.twitter?.apiSecret,
credentials.twitter?.accessToken,
credentials.twitter?.accessTokenSecret
);
this.linkedinAPI = new LinkedInAPI(credentials.linkedin?.accessToken);
this.instagramAPI = new InstagramAPI(credentials.instagram?.accessToken);
this.enabledPlatforms = new Set(Object.keys(credentials));
}
async postToAllPlatforms(content, options = {}) {
console.log('š Starting cross-platform posting...\n');
const {
imageUrl,
hashtags = [],
platforms = Array.from(this.enabledPlatforms),
articleUrl
} = options;
const results = [];
const errors = [];
// Post to each platform in parallel
const promises = platforms.map(async (platform) => {
try {
if (!this.enabledPlatforms.has(platform)) {
throw new Error(`Platform ${platform} not configured`);
}
let result;
switch (platform) {
case 'facebook':
const fbContent = ContentFormatter.addHashtags(
ContentFormatter.formatForPlatform(content, 'facebook', 2000),
hashtags
);
result = await this.facebookAPI.postToFeed(fbContent, imageUrl);
break;
case 'twitter':
const twContent = ContentFormatter.addHashtags(
ContentFormatter.formatForPlatform(content, 'twitter', 240),
hashtags.slice(0, 3) // Limit hashtags for Twitter
);
let mediaIds = [];
if (imageUrl) {
const mediaId = await this.twitterAPI.uploadMedia(imageUrl);
mediaIds.push(mediaId);
}
result = await this.twitterAPI.createTweet(twContent, mediaIds);
break;
case 'linkedin':
const liContent = ContentFormatter.addHashtags(
ContentFormatter.formatForPlatform(content, 'linkedin', 1300),
hashtags
);
result = await this.linkedinAPI.sharePost(liContent, articleUrl);
break;
case 'instagram':
if (!imageUrl) {
throw new Error('Instagram requires an image');
}
const igContent = ContentFormatter.addHashtags(
ContentFormatter.formatForPlatform(content, 'instagram', 2200),
hashtags
);
result = await this.instagramAPI.createPost(imageUrl, igContent);
break;
default:
throw new Error(`Unsupported platform: ${platform}`);
}
results.push(result);
return result;
} catch (error) {
console.log(`ā Error posting to ${platform}: ${error.message}`);
errors.push({ platform, error: error.message });
return null;
}
});
await Promise.allSettled(promises);
console.log('\nš Cross-platform posting summary:');
console.log(`ā
Successful posts: ${results.length}`);
console.log(`ā Failed posts: ${errors.length}`);
return {
success: results.length > 0,
results,
errors,
summary: {
total: platforms.length,
successful: results.length,
failed: errors.length
}
};
}
async getUnifiedAnalytics(postIds) {
console.log('š Gathering analytics from all platforms...\n');
const analytics = [];
for (const postData of postIds) {
try {
let metrics;
switch (postData.platform) {
case 'facebook':
metrics = await this.facebookAPI.getAnalytics(postData.id);
break;
case 'twitter':
metrics = await this.twitterAPI.getTweetMetrics(postData.id);
break;
case 'linkedin':
metrics = await this.linkedinAPI.getPostStats(postData.id);
break;
case 'instagram':
metrics = await this.instagramAPI.getInsights(postData.id);
break;
default:
continue;
}
analytics.push(metrics);
} catch (error) {
console.log(`ā Error fetching analytics for ${postData.platform}: ${error.message}`);
}
}
// Calculate unified metrics
const totalEngagement = analytics.reduce((sum, metric) => {
const platformEngagement = (metric.likes || 0) +
(metric.shares || metric.retweets || 0) +
(metric.comments || metric.replies || 0);
return sum + platformEngagement;
}, 0);
console.log('\nš Unified Analytics Summary:');
console.log(`Total Engagement: ${totalEngagement}`);
console.log(`Platforms Analyzed: ${analytics.length}`);
return {
totalEngagement,
platformAnalytics: analytics,
summary: {
platformCount: analytics.length,
averageEngagement: Math.round(totalEngagement / analytics.length) || 0
}
};
}
async getAllProfiles() {
console.log('š„ Fetching profiles from all platforms...\n');
const profiles = [];
const profilePromises = Array.from(this.enabledPlatforms).map(async (platform) => {
try {
let profile;
switch (platform) {
case 'facebook':
profile = await this.facebookAPI.getUserProfile();
break;
case 'twitter':
profile = await this.twitterAPI.getUserInfo();
break;
case 'linkedin':
profile = await this.linkedinAPI.getProfile();
break;
case 'instagram':
profile = await this.instagramAPI.getUserData();
break;
}
if (profile) profiles.push(profile);
} catch (error) {
console.log(`ā Error fetching ${platform} profile: ${error.message}`);
}
});
await Promise.allSettled(profilePromises);
const totalFollowers = profiles.reduce((sum, profile) => {
return sum + (profile.followers || profile.connections || 0);
}, 0);
console.log('\nš„ Profile Summary:');
console.log(`Total Reach: ${totalFollowers.toLocaleString()} followers/connections`);
return {
profiles,
totalReach: totalFollowers,
platformCount: profiles.length
};
}
async schedulePost(content, scheduledTime, options = {}) {
console.log(`ā° Scheduling post for ${scheduledTime}...`);
// In a real implementation, this would integrate with a job scheduler
const delay = new Date(scheduledTime).getTime() - Date.now();
if (delay <= 0) {
console.log('ā” Scheduled time is in the past, posting immediately...');
return await this.postToAllPlatforms(content, options);
}
console.log(`š
Post scheduled for ${new Date(scheduledTime).toLocaleString()}`);
return {
scheduled: true,
scheduledTime,
content: content.substring(0, 50) + '...',
platforms: options.platforms || Array.from(this.enabledPlatforms)
};
}
}
// Demo usage
async function socialMediaDemo() {
console.log('š± Social Media Integration Facade Demo\n');
// Mock credentials
const credentials = {
facebook: { accessToken: 'fb_mock_token' },
twitter: {
apiKey: 'tw_mock_key',
apiSecret: 'tw_mock_secret',
accessToken: 'tw_mock_token',
accessTokenSecret: 'tw_mock_token_secret'
},
linkedin: { accessToken: 'li_mock_token' },
instagram: { accessToken: 'ig_mock_token' }
};
const socialMedia = new SocialMediaFacade(credentials);
// Example 1: Cross-platform posting
console.log('=== Cross-Platform Posting ===');
const postResult = await socialMedia.postToAllPlatforms(
"Excited to share our new product launch! Check out the amazing features and let us know what you think. #innovation #productlaunch #excited",
{
imageUrl: 'https://example.com/product-image.jpg',
hashtags: ['innovation', 'productlaunch', 'excited', 'newproduct'],
articleUrl: 'https://example.com/blog/product-launch'
}
);
console.log('\nPost Results:', JSON.stringify(postResult, null, 2));
// Example 2: Analytics gathering
if (postResult.results.length > 0) {
console.log('\n=== Analytics Demo ===');
const postIds = postResult.results.map(result => ({
platform: result.platform,
id: result.postId || result.tweetId || result.shareId
}));
const analytics = await socialMedia.getUnifiedAnalytics(postIds);
console.log('Analytics Results:', JSON.stringify(analytics, null, 2));
}
// Example 3: Profile information
console.log('\n=== Profile Information ===');
const profiles = await socialMedia.getAllProfiles();
console.log('Profile Results:', JSON.stringify(profiles, null, 2));
// Example 4: Scheduled posting
console.log('\n=== Scheduled Posting ===');
const futureDate = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours from now
const scheduledResult = await socialMedia.schedulePost(
"Don't forget about our webinar tomorrow! Register now to secure your spot.",
futureDate,
{
platforms: ['facebook', 'twitter', 'linkedin'],
hashtags: ['webinar', 'education', 'registration']
}
);
console.log('Scheduled Post Result:', JSON.stringify(scheduledResult, null, 2));
}
// Run the demo
socialMediaDemo().catch(console.error);
6. Hands-On Practice (30-40 minutes)
Exercise 1: Banking System Facade (15 minutes)
Objective: Create a facade for a complex banking system with multiple services
Instructions:
- Create subsystem classes:
AccountService,TransactionService,NotificationService,AuditService - Design a
BankingFacadethat provides simple methods liketransferMoney(),checkBalance(),payBill() - Each facade method should coordinate multiple subsystems appropriately
Starter Code:
class AccountService {
public boolean validateAccount(String accountNumber) { /* implementation */ }
public double getBalance(String accountNumber) { /* implementation */ }
public void debit(String accountNumber, double amount) { /* implementation */ }
public void credit(String accountNumber, double amount) { /* implementation */ }
}
class TransactionService {
public String createTransaction(String fromAccount, String toAccount, double amount) { /* implementation */ }
public void recordTransaction(String transactionId, String details) { /* implementation */ }
}
// Your task: Create BankingFacade that coordinates these services
Solution:
class BankingFacade {
private AccountService accountService;
private TransactionService transactionService;
private NotificationService notificationService;
private AuditService auditService;
public BankingFacade() {
this.accountService = new AccountService();
this.transactionService = new TransactionService();
this.notificationService = new NotificationService();
this.auditService = new AuditService();
}
public boolean transferMoney(String fromAccount, String toAccount, double amount, String description) {
try {
// Validate accounts
if (!accountService.validateAccount(fromAccount) || !accountService.validateAccount(toAccount)) {
return false;
}
// Check sufficient balance
if (accountService.getBalance(fromAccount) < amount) {
notificationService.sendInsufficientFundsAlert(fromAccount);
return false;
}
// Create transaction record
String transactionId = transactionService.createTransaction(fromAccount, toAccount, amount);
// Perform transfer
accountService.debit(fromAccount, amount);
accountService.credit(toAccount, amount);
// Record transaction details
transactionService.recordTransaction(transactionId, description);
// Send notifications
notificationService.sendTransferConfirmation(fromAccount, toAccount, amount, transactionId);
// Audit logging
auditService.logTransaction(transactionId, fromAccount, toAccount, amount);
return true;
} catch (Exception e) {
auditService.logError("Transfer failed: " + e.getMessage());
return false;
}
}
}
Exercise 2: Smart Home Control Facade (25 minutes)
Objective: Design a facade for managing multiple smart home devices and systems
Requirements:
- Control lighting, security, climate, and entertainment systems
- Create “scenes” that coordinate multiple devices (e.g., “Good Night”, “Movie Time”)
- Provide simple voice command-style methods
- Handle device failures gracefully
Challenge: Implement a scene scheduler that can activate different scenes at different times
Extension Ideas:
- Add energy monitoring and optimization features
- Implement device health monitoring
- Create adaptive scenes based on user preferences and habits
7. Common Pitfalls and Troubleshooting (10 minutes)
Frequent Mistakes
-
Problem: Facade becomes a “god object” with too many responsibilities Cause: Trying to centralize all functionality in a single facade Solution: Create multiple specialized facades for different domains or use cases
-
Problem: Facade tightly couples to specific subsystem implementations Cause: Direct instantiation and reference to concrete classes Solution: Use dependency injection and depend on abstractions/interfaces
-
Problem: Clients bypassing the facade and accessing subsystems directly Cause: Facade doesn’t provide sufficient functionality or performance Solution: Ensure facade covers common use cases; allow direct access when needed
-
Problem: Facade hides too much, making debugging difficult Cause: Overly simplistic error handling and logging Solution: Provide adequate logging and error propagation; consider exposing more detailed methods for advanced users
Debugging Tips
- Add comprehensive logging in facade methods to trace subsystem interactions
- Provide debug modes that expose more detailed information about subsystem calls
- Use consistent error handling patterns across all facade methods
- Test facade methods independently and with mocked subsystems
- Monitor facade performance to identify subsystem bottlenecks
8. Best Practices and Professional Tips (5 minutes)
Industry Standards
- Keep facades stateless when possible for better scalability and testability
- Use dependency injection to make facades more flexible and testable
- Implement proper error handling with meaningful error messages and codes
- Provide both simple and advanced methods to cater to different user needs
Design Principles
- Single Responsibility: Each facade should focus on a specific domain or workflow
- Interface Segregation: Create multiple focused facades rather than one large facade
- Dependency Inversion: Depend on abstractions, not concrete implementations
- Don’t Repeat Yourself: Facades should eliminate duplicate coordination logic
Performance Considerations
- Cache frequently accessed data to reduce subsystem calls
- Use asynchronous operations when coordinating multiple independent subsystems
- Implement circuit breakers for external service dependencies
- Consider lazy loading for expensive subsystem initializations
When to Use Alternatives
- Mediator Pattern: When you need complex communication between multiple objects
- Proxy Pattern: When you need to control access or add cross-cutting concerns
- Adapter Pattern: When you need to make incompatible interfaces work together
- Service Layer Pattern: For business logic coordination in enterprise applications
Next Steps in Your Learning Journey
- Study Mediator Pattern for complex object interactions
- Learn Proxy Pattern for access control and lazy loading
- Explore Service-Oriented Architecture (SOA) principles
- Practice with API Gateway patterns in microservice architectures
9. Summary and Review (5 minutes)
Key Takeaways
- Facade pattern simplifies complex subsystems by providing unified interfaces
- Coordination and error handling are centralized in the facade layer
- Clients remain loosely coupled to subsystem implementations
- Multiple facades can coexist for different use cases or user types
Quick Review Questions
- How does Facade pattern differ from Adapter pattern in terms of intent?
- When would you create multiple facades instead of one large facade?
- What are the trade-offs between simplicity and flexibility in facade design?
For Instructors
Teaching Tips
- Start with a relatable complex system like a car or home theater
- Show the before/after - complex client code vs simple facade usage
- Emphasize coordination aspects - facades do more than just delegate
- Demonstrate error handling strategies in facade methods
- Use real-world examples from popular frameworks and APIs
Common Student Questions
- “Should facades be singletons?” - Not necessarily; depends on state and lifecycle requirements
- “Can clients still access subsystems directly?” - Yes, facades don’t prevent direct access
- “How is this different from a service layer?” - Very similar concepts; facades focus more on simplification
Assessment Ideas
- System integration projects requiring facade design
- Refactoring exercises converting complex client code to use facades
- API design challenges creating user-friendly interfaces for complex systems
- Performance analysis comparing direct subsystem access vs facade coordination
Adaptation Notes
- For beginners: Focus on simple delegation before complex coordination
- For advanced students: Explore facade patterns in microservice architectures
- Language-specific considerations: Discuss static vs instance methods for facade implementations
- Time management: The hands-on exercises can be scaled based on available time and student experience level