Un alt design pattern interesant din categoria celor structurale este facade design pattern, sau fațadă, tradus în limba română. Cred că numele îi spune tot, nu? O fațadă care stă în fața unor detalii care nu sunt de interes pentru cel care o privește. Exact ăsta e și rolul lui facade, singura diferență fiind că nu are numai rol vizual să spunem ci și unul funcțional.

Facade Design Pattern

Știu că diagrama de mai sus pare mai complexă, dar a fost creată intenționat așa. Facade prin definiție are rolul de a ascunde complexitatea unei colecții de clase, sau subsisteme, pentru a facilita ușurința în utilizare pentru cel ce joacă rolul de client. Bineînțeles că exemplul dat de mine este mult simplificat și are rolul doar de a reprezenta ideea din spate sub forma unei analogii.

Problema

Așa cum probabil ai intuit, imaginează-ți că ai dezvoltat o colecție de subsisteme care interacționează între ele, având rolul de infrastructură a unui magazin online.

Acum, infrastructura ta trebuie să fie folosită de o aplicație client-side (web, mobile, etc.) pentru a prelua comenzile utilizatorilor și să le ofere un status în legătură cu acestea. Dacă ar fi să apeleze fiecare subsistem în parte, aplicația client ar fi de o complexitate enormă, greu de făcut mentenanță atunci când întâmpini probleme, greu de adăugat features noi.

Rezolvare

Și atunci ce-i de făcut? Creezi un fel de fațadă care joacă rolul de orchestrator pentru subsistemele tale, iar aplicația ta client nu va avea nevoie să discute decât cu acest layer. Așa cum poți vedea mai jos, OrderOrchestrator are o singură metodă pe care o pune la dispoziție codului client, handleOrder.

public class OrderOrchestrator {
    // Use DI container here, creating instances for simplification.
    private final OrderProcessor processor;
    private final OrderPreparator preparator;
    private final Transporter transporter;

    public OrderOrchestrator(OrderProcessor processor, OrderPreparator preparator, Transporter transporter) {
        this.processor = processor;
        this.preparator = preparator;
        this.transporter = transporter;
    }

    public void handleOrder(Order order){
        try {
            processor.process(order);
        } catch (Exception e) {
            System.out.println("Order is invalid: " + e.getMessage());
        }
        preparator.prepare(order);
        transporter.load(order);
        transporter.finish();
    }
}

Știu că pare că sunt înghesuite o grămadă de dependințe în clasa asta, dar din fericire injectarea dependințelor nu este treaba aplicației client side. Rolul ei este doar să transmită comanda (sau Order-ul) către Orchestrator, cel mai probabil sub forma unui POST request.

public class Order {
    private int id;
    private OrderState state;
    private String customerFirstName;
    private String customerLastName;
    private String deliveryAddress;
    private boolean paid;
    private List<Product> products;

    public int getId() {
        return id;
    }

    public OrderState getState() {
        return state;
    }

    public void setState(OrderState state) {
        this.state = state;
    }

    public String getCustomerFirstName() {
        return customerFirstName;
    }

    public void setCustomerFirstName(String customerFirstName) {
        this.customerFirstName = customerFirstName;
    }

    public String getCustomerLastName() {
        return customerLastName;
    }

    public void setCustomerLastName(String customerLastName) {
        this.customerLastName = customerLastName;
    }

    public String getDeliveryAddress() {
        return deliveryAddress;
    }

    public void setDeliveryAddress(String deliveryAddress) {
        this.deliveryAddress = deliveryAddress;
    }

    public double getPrice() {
        double price = 0;
        for(Product p : products){
            price += p.getPrice();
        }
        return price;
    }

    public boolean isPaid() {
        return paid;
    }

    public void setPaid(boolean paid) {
        this.paid = paid;
    }

    public List<Product> getProducts() {
        return products;
    }

    public void setProducts(List<Product> products) {
        this.products = products;
    }
}

Order-ul preluat din aplicația client este delegat mai departe către OrderProcessor.

public class OrderProcessor {
    private Order order;

    public void process(Order order) throws Exception{
        this.order = order;
        if(order.getState() == OrderState.Pending){
            if(hasCustomerName() && hasDeliveryAddress()){
                order.setState(OrderState.Processed);
            }else{
                throw new IllegalArgumentException("Order data incomplete.");
            }
        }
        throw new IllegalArgumentException("Order is in invalid state.");
    }

    private boolean hasCustomerName(){
        String customerFullName = String.format("%s %s", order.getCustomerFirstName(), order.getCustomerLastName());
        return !customerFullName.isEmpty() || !customerFullName.isBlank();
    }

    private boolean hasDeliveryAddress(){
        return !order.getDeliveryAddress().isEmpty() || order.getDeliveryAddress().isBlank();
    }
}

OrderPreparator joacă aici rolul depozitului, să spunem, care pregătește comanda pentru livrare. De aici și dependința către StockManager.

public class StockManager {
    private final List<Product> products = Arrays.asList(
            new Product("123", "Shoes", 150, 100),
            new Product("456", "T-Shirt", 300, 49),
            new Product("789", "Jeans", 420, 150),
            new Product("910", "Hat", 800, 35),
            new Product("321", "Gloves", 2300, 20),
            new Product("654", "Sun Glasses", 3200, 99)
    );

    public boolean hasStock(String barcode, int necessaryQuantity){
        return products.stream().anyMatch(p ->
                p.getBarcode().equals(barcode) &&
                p.getQuantity() <= necessaryQuantity);
    }

    public void increaseStock(String barcode, int receivedQuantity){
        Product product = findProduct(barcode);
        product.setQuantity(product.getQuantity() + receivedQuantity);
    }

    public void decreaseStock(String barcode, int quantity){
        Product product = findProduct(barcode);
        if(!hasStock(barcode, quantity)){
            product.setQuantity(product.getQuantity());
            System.out.printf("Warning! Product %s has less stock than ordered quantity", product.getName());
        }
    }

    private Product findProduct(String barcode){
        return products.stream().filter(p ->
                p.getBarcode().equals(barcode))
                .findFirst()
                .orElseThrow();
    }
}

StockManager este subsistemul responsabil cu stocarea produselor și stocurilor în baza de date.

public class Transporter {
    private Order order;

    public void load(Order order){
        System.out.println("Order loaded");
        order.setState(OrderState.InTransit);
    }

    public void finish(){
        if(order.isPaid()){
            System.out.printf("Order has been delivered to %s", order.getDeliveryAddress());
            order.setState(OrderState.Delivered);
        }
    }
}

Iar Transporter este responsabil cu transportul comenzii la client. Imaginează-ți toate clasele descrise mai sus ca fiind subsisteme mai mici sau mai mari, ale unui magazin online. Dacă OrderOrchestrator nu ar fi fost, complexitatea aplicației client ar fi crescut vertiginos, pentru că ar fi trebuit să știe cui să paseze comanda pentru procesare, cine o să pregătească și o să transporte comanda și mai mult de atât, dacă este stoc în depozit pentru produsele comandate.

Exemplul simplifică mult lucrurile, dar cred că ai înțeles care este de fapt ideea. O situație similară poate fi dacă ai un SDK sau o librărie foarte complexă, ale cărei clase au nevoie de un anumit state pentru fi apelate, de anumite dependințe și așa mai departe. Când ai o situație de genul, adaugi deasupra acestor clase una sau mai multe clase mult simplificate și în felul ăsta ai ușurat mult munca celui care apelează librăria ta.