Skip to content

Facade

The facade is another design pattern whose main purpose is to provide a interface that:

  • it simplifies the performance of a certain operation by taking several steps that are required to obtain a certain result. Most often, these steps are method calls on separate objects.
  • it integrates many interfaces into one common one, which is used e.g. by the end user of a given library.

The use of the facade is associated with the use of (business) related objects that have been divided, according to the rules SOLID, and most of all the Single Responsibility principle.

Example 1

The example shows how to create a simple facade to perform the ordering operation. Such an operation requires:

  • check if the ordered product is available
  • pay for the product
  • delivery of the product

facade

An appropriate interface is responsible for each of the above operations:

  • DeliveryService
  • PaymentService
  • ProductAvailabilityService

Facade uses these three interfaces (following the SOLID - dependency injection principle) to perform an operation with a single method call:

class DeliveryService:
    def deliver_product(self, product_id, amount, recipient):
        pass
class PaymentService:
    def pay(self, product_id, amount):
        pass
class ProductAvailabilityService:
    def is_available(self, product_id):
        pass
class OrderFacade:
    def __init__(self, delivery_service, payment_service, product_availability_service):
        self._delivery_service = delivery_service
        self._payment_service = payment_service
        self._product_availability_service = product_availability_service

    def place_order(self, product_id, amount, recipient):
        if self._product_availability_service.is_available(product_id):
            self._payment_service.pay(product_id, amount)
            self._delivery_service.deliver_product(product_id, amount, recipient)

Example 2

The following example shows a slightly different approach to the facade.

facade

This example includes three implementations of the Encryptor interface, which hashes the input string in some way. This interface has three implementations:

  • BCryptEncryptor - which (in real implementation) uses the BCrypt algorithm to hashing input characters
  • SCryptEncryptor - which uses the SCrypt algorithm
  • NoOpEncryptor - which does not change the input characters.

We want to "relieve" the end user from using specific implementations of the Encryptor interface, and we want them to be able to use the Encryptors interface, which hashes input as appropriate. EncryptionFacade is its implementation:

class Encryptor:
    def encrypt(self, to_encrypt):
        pass
class BCryptEncryptor(Encryptor):
    def encrypt(self, to_encrypt):
        return f"encrypting {to_encrypt} with BCrypt"
class SCryptEncryptor(Encryptor):
    def encrypt(self, to_encrypt):
        return f"encrypting {to_encrypt} with SCrypt"
class NoOpEncryptor(Encryptor):
    def encrypt(self, to_encrypt):
        return to_encrypt
class Encryptors:
    def encrypt_without_modification(self, to_encrypt):
        pass

    def encrypt_with_bcrypt(self, to_encrypt):
        pass

    def encrypt_with_scrypt(self, to_encrypt):
        pass
class EncryptionFacade(Encryptors):
    def __init__(self, scrypt_encryptor, bcrypt_encryptor, noop_encryptor):
        self._scrypt_encryptor = scrypt_encryptor
        self._bcrypt_encryptor = bcrypt_encryptor
        self._noop_encryptor = noop_encryptor

    def encrypt_without_modification(self, to_encrypt):
        return self._noop_encryptor.encrypt(to_encrypt)

    def encrypt_with_bcrypt(self, to_encrypt):
        return self._bcrypt_encryptor.encrypt(to_encrypt)

    def encrypt_with_scrypt(self, to_encrypt):
        return self._scrypt_encryptor.encrypt(to_encrypt)

Summing up, both of the above examples show that the main purpose of using a facade is to reduce the complexity of operations performed on related objects or to hide implementation details.