Facade is a structural design pattern provides a simple interface for client to interact with a complex system. The system internally might have lots of different classes and methods. Facade provides a simple abstraction on that system or library. This tutorial explains Facade with practical Java examples.

What is Facade?

This is a design pattern that involves encapsulating a complex system in a simple interface.

If you have built a system which uses 3rd party API or library and is tied to their implementation of specific functionality, you might have various different classes initialized to meet the requirements of the API provided by that library. Those classes and objects may not be relevant for your use case but you still needed follow the API. The end user for your application might only care about specific set of functionalities and they do not need to care about those details. In this case, Facade can provide a very simple interface for the end users of your application which hides the complexity of the system. Facade usually includes only the required features that the client wants.

For example, complex ecommerce web applications might have order placement service. Behind the scenes those services might be interacting with third party services like Stripe. Although Stripe might have lots of different funtionalities to have payment option, the order placement service might only need a simple function which has method like charge(double amount, CreditCard cc) which charges a creditcard with specific amount of money. This is an example of Facade design pattern. You might have already applied this design pattern several times without even noticing it.

In banking, when you want to contact bank operator with a phone, you might go through automated system, that’s also an example of real world facae. It hides lots of different functions that bank might be performing but you get simple options to choose.

How to implement Facade pattern?

  1. Identify the complex subsystem and if it’s possible to provide simpler interface for the client code.
  2. Identify and define the Facade interface that you want to provide for the client. This may or may not mean actually declaring an interface in Java. Implement this interface in a new class. This Facade should actually interact with subsystem but client interacts only with this Facade. Facade should have instance of the subsystem to work with it.
  3. Make client code communicate with Facade rather than subsystem code. This way we decouple subsystem from the client and it becomes easier to update Facade to user newer API when subsystem updates to newer version.

In standard Java library, java.net.URL is a great example of Facade pattern. It hides the complexity of interacting with different protocols. It can work with FTP, HTTP, HTTPs, etc. without client directly interacting with those handlers.

Facade for Payment Service

Let’s say we want to provide various payment options for our customers, but we want to hide the implementation of our different payment processors.

1public interface PaymentService {
2    boolean pay(double amount, String accountNumber);
3}

We have concrete payment processors for Debit card, credit card and gift card that handles payment methods of respective card type.

1public class DebitCardPayment implements PaymentService {
2    @Override
3    public boolean pay(double amount, String accountNumber) {
4        System.out.println("Paying $" + amount + " using debit card " + accountNumber);
5        return true;
6    }
7}
1public class CreditCardPayment implements PaymentService {
2    @Override
3    public boolean pay(double amount, String accountNumber) {
4        System.out.println("Paying $" + amount + " using credit card " + accountNumber);
5        return true;
6    }
7}
1public class GiftCardPayment implements PaymentService {
2    @Override
3    public boolean pay(double amount, String accountNumber) {
4        System.out.println("Paying $" + amount + " using gift card " + accountNumber);
5        return true;
6    }
7}

For client, we want to provide a simple makePayment() method which takes the credit card number, card type and the amount which needs to be charged. For this, we can create a Payment Facade which will handle respective payment type. It will have instance of respective PaymentService.

 1public class PaymentServiceFacade {
 2    private CreditCardPayment creditCardPayment;
 3    private DebitCardPayment debitCardPayment;
 4    private GiftCardPayment giftCardPayment;
 5
 6    public PaymentServiceFacade() {
 7        creditCardPayment = new CreditCardPayment();
 8        debitCardPayment = new DebitCardPayment();
 9        giftCardPayment = new GiftCardPayment();
10    }
11
12   public boolean makePayment(double amount, String accountNumber, String paymentType) {
13        if (paymentType.equals("credit")) {
14            return creditCardPayment.pay(amount, accountNumber);
15        } else if (paymentType.equals("debit")) {
16            return debitCardPayment.pay(amount, accountNumber);
17        } else if (paymentType.equals("gift")) {
18            return giftCardPayment.pay(amount, accountNumber);
19        } else {
20            return false;
21        }
22    }
23}

Here PaymentServiceFacade provides a high level method makePayment() for the client and it decides on the concrete payment method which needs to handle the payment behind the scene.

Facade with Order Management

Another complex system could be order management. When we place an order, there are lots of checks that need to go through before order is accepted and money being charged. We can hide these complexities using a facade. It needs to handle Inventory to update inventory and verify if we have enough inventory to take this order, it has to actually create an order in the system, it has to charge the credit card or other form of payment and it has to notify the user that their order has been successfully place and further a shipping team has to be notified to ship this product promptly. All these complexities could be hiddden with simple placeOrder() method in Order management facade.

 1interface InventoryService {
 2    void update(String productId, int quantity);
 3    boolean isAvailable(String productId, int quantity);
 4}
 5
 6class InventoryServiceImpl implements InventoryService{
 7    private Map<String, Integer> inventory;
 8
 9    public InventoryServiceImpl() {
10        this.inventory = new HashMap<>();
11        populateInventory();
12    }
13
14    private void populateInventory() {
15        inventory.put("A", 100);
16        inventory.put("B", 10);
17        inventory.put("C", 1000);
18        inventory.put("D", 0);
19    }
20
21    @Override
22    public void update(String productId, int quantity) {
23        System.out.println("Updating inventory for product " + productId + " quantity " + quantity);
24    }
25
26    @Override
27    public boolean isAvailable(String productId, int quantity) {
28        int itemsAvailable = inventory.getOrDefault(productId, 0);
29        return itemsAvailable >= quantity;
30    }
31}
 1interface OrderProcessingService {
 2    void createOrder(String product, int quantity, String customerInfo);
 3}
 4
 5class OrderProcessingServiceImpl implements OrderProcessingService{
 6    @Override
 7    public void createOrder(String product, int quantity, String customerInfo) {
 8        // Business logic to create an order
 9        // Add entry into backend database (systems)
10        System.out.println("Order created for product - " + product + " with quantity - " + quantity + " and customer info - " + customerInfo);
11    }
12}
 1interface PaymentService {
 2    boolean processPayment(double amount, String creditCardDetails);
 3}
 4
 5class PaymentServiceImpl implements PaymentService{
 6    @Override
 7    public boolean processPayment(double amount, String creditCardDetails) {
 8        System.out.println("Payment processed for amount " + amount + " using credit card " + creditCardDetails);
 9        return true;
10    }
11}
 1interface NotificationService {
 2    void sendOrderConfirmation(String orderNumber);
 3    void sendOrderShipped(String orderNumber);
 4    void sendOrderDelivered(String orderNumber);
 5    void sendOrderCancelation(String orderNumber);
 6}
 7
 8class NotificationServiceImpl implements NotificationService{
 9    @Override
10    public void sendOrderConfirmation(String orderNumber) {
11        // Inform shipping department or shipping provider to ship the product
12        System.out.println("Your order has been placed for order number: " + orderNumber);
13    }
14
15    @Override
16    public void sendOrderShipped(String orderNumber) {
17        System.out.println("Order shipped for order number " + orderNumber);
18    }
19
20    @Override
21    public void sendOrderDelivered(String orderNumber) {
22        System.out.println("Order delivered for order number " + orderNumber);
23    }
24
25    @Override
26    public void sendOrderCancelation(String orderNumber) {
27        System.out.println("Order cancellation sent for order number " + orderNumber);
28    }
29}
 1interface ShippingService {
 2    void shipOrder(String productId, String shippingAddress);
 3}
 4
 5class ShippingServiceImpl implements ShippingService {
 6    @Override
 7    public void shipOrder(String productId, String shippingAddress) {
 8        System.out.println("Shipping product " + productId + " to address: " + shippingAddress);
 9    }
10}

For this example, we have five services which will be used when a user places an order. This is relatively simple design. In practice, it will be lot more complicated. We can create OrdersFacade which will coordinate with these classes to place an order.

 1public class OrdersFacade {
 2    private OrderProcessingService orderProcessingService;
 3    private NotificationService notificationService;
 4    private PaymentService paymentService;
 5    private ShippingService shippingService;
 6    private InventoryService inventoryService;
 7
 8    public OrdersFacade() {
 9        this.orderProcessingService = new OrderProcessingServiceImpl();
10        this.notificationService = new NotificationServiceImpl();
11        this.paymentService = new PaymentServiceImpl();
12        this.shippingService = new ShippingServiceImpl();
13        this.inventoryService = new InventoryServiceImpl();
14    }
15
16    public void placeOrder(String productId, int quantity, String customerInfo, String shippingAddress, String creditCardDetails) {
17        String orderId = "1234";
18        if (!inventoryService.isAvailable(productId, quantity)) {
19            notificationService.sendOrderCancelation(orderId);
20            return;
21        }
22        orderProcessingService.createOrder(productId, quantity, customerInfo);
23        if (paymentService.processPayment(100.0, creditCardDetails)) {
24            notificationService.sendOrderConfirmation("1234");
25            shippingService.shipOrder(productId, shippingAddress);
26        } else {
27            notificationService.sendOrderCancelation("1234");
28        }
29    }
30}

The client will need to only call OrdersFacade object with placeOrder() method.

1public class Client {
2    public static void main(String[] args) {
3        OrdersFacade ordersFacade = new OrdersFacade();
4        ordersFacade.placeOrder("B", 10, "John Doe", "1 Master St, San Jose, CA, United States", "1234-5678-4321-8765");
5    }
6}

Advantages:

  • This patten helps reduce the complexity of the system and is great for refactoring.
  • It enables client code to become loosely coupled with the complex system by providing middle layer which can easily be modified when third party implementation changes.
  • Facade makes code more readable by providing simple function which will have descriptive names to understand what facade is doing.

Disadvantages:

  • It can add additional layer of code.
  • Adds little performance overhead due to additional facade middle layer

Summary

  • This pattern hides the complex details of the system for the client.
  • This is very easy to implement as there are no constraints on how to implement it.
  • We can also create facade classes which further hides another facade complexity.
  • It is also considered one of the refactoring pattern as it’s commonly applied during refactor process.