Facade¶
The facade is another design pattern where the main purpose is to provide an interface that:
- simplifies the performance of a certain operation by taking a few steps that are required to obtain a certain result. Most often, these steps are method calls on separate objects.
- integrates many interfaces into one common one e.g. used by the end-user of a given library.
The use of the facade involves the use of business related objects that have been divided, e.g. according to the SOLID principles (above 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:
- to check if the ordered product is available
- to pay for the product
- to deliver the product
An appropriate interface is responsible for each of the above operations:
DeliveryService
PaymentService
ProductAvailabilityService
Facade uses these three interfaces (respecting the SOLID principle - dependency injection
) to perform an operation with a single method call:
public interface DeliveryService {
void deliverProduct(Long productsId, int amount, String recipient);
}
public interface PaymentService {
void pay(Long productId, int amount);
}
public interface ProductAvailabilityService {
boolean isAvailable(Long productId);
}
public class OrderFacade {
private final DeliveryService deliveryService;
private final PaymentService paymentService;
private final ProductAvailabilityService productAvailabilityService;
public OrderFacade(final DeliveryService deliveryService, final PaymentService paymentService, final ProductAvailabilityService productAvailabilityService) {
this.deliveryService = deliveryService;
this.paymentService = paymentService;
this.productAvailabilityService = productAvailabilityService;
}
public boolean placeOrder(final Long productId, final int amount, final String recipient) {
if (productAvailabilityService.isAvailable(productId)) {
paymentService.pay(productId, amount);
deliveryService.deliverProduct(productId, amount, recipient);
return true;
}
return false;
}
}
Example 2¶
The following example shows a slightly different approach to the facade pattern.
This example includes three implementations of the Encryptor
interface, which hashes the inputString
in some way. This interface has three implementations:
BCryptEncryptor
- which (in real implementation) uses the BCrypt algorithm to hash input charactersSCryptEncryptor
- which uses the SCrypt algorithmNoOpEncryptor
- which does not change input characters.
Instead, we want to "free" the end-user from using specific implementations of the Encryptor
interface. We want to give the ability to use the Encryptors
interface, which hashes the input according to the appropriate method chosen. EncryptionFacade
is its implementation:
public interface Encryptor {
String encrypt(String toEncrypt);
}
public class BCryptEncryptor implements Encryptor {
@Override
public String encrypt(final String toEncrypt) {
return "encrypting " + toEncrypt + "with BCrypt";
}
}
public class SCryptEncryptor implements Encryptor {
@Override
public String encrypt(final String toEncrypt) {
return "encrypting " + toEncrypt + "with SCrypt";
}
}
public class NoOpEncryptor implements Encryptor {
@Override
public String encrypt(final String toEncrypt) {
return toEncrypt;
}
}
public interface Encryptors {
String encryptWithoutModification(final String toEncrypt);
String encryptWithBCrypt(final String toEncrypt);
String encryptWithSCrypt(final String toEncrypt);
}
public class EncryptionFacade implements Encryptors {
private final SCryptEncryptor sCryptEncryptor;
private final BCryptEncryptor bCryptEncryptor;
private final NoOpEncryptor noOpEncryptor;
public EncryptionFacade(final SCryptEncryptor sCryptEncryptor, final BCryptEncryptor bCryptEncryptor, final NoOpEncryptor noOpEncryptor) {
this.sCryptEncryptor = sCryptEncryptor;
this.bCryptEncryptor = bCryptEncryptor;
this.noOpEncryptor = noOpEncryptor;
}
@Override
public String encryptWithoutModification(final String toEncrypt) {
return noOpEncryptor.encrypt(toEncrypt);
}
@Override
public String encryptWithBCrypt(final String toEncrypt) {
return bCryptEncryptor.encrypt(toEncrypt);
}
@Override
public String encryptWithSCrypt(final String toEncrypt) {
return sCryptEncryptor.encrypt(toEncrypt);
}
}
In conclusion, 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 hide implementation details.