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
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.
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 charactersSCryptEncryptor
- which uses the SCrypt algorithmNoOpEncryptor
- 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.