Facade (Фасад)
Facade предоставляет упрощённый интерфейс для доступа к системе.
Фасад используется, когда в системе есть набор компонентов, которые взаимодействуют друг с другом. Клиентскому коду не всегда требуется использовать все эти компоненты – иногда ему нужна лишь высокоуровневая операция.
Пример
Представим, что у нас есть интернет-магазин. Основная фича, которая нужна покупателю – сделать заказ.
Но при этом бизнес-процессы интернет-магазина устроены сложно. Он состоит из многих подсистем, таких как: склад, доставка, колл-центр и так далее.
Клиенту совершенно не нужно знать, как это всё работает «под капотом». Как мы уже сказали – всё, что ему нужно это сделать заказ.
Поэтому мы можем представить функционал работы с заказом в виде фасада:
package ecommerce;
public final class OrderFacade {
private final InventoryService inventoryService;
private final PaymentService paymentService;
private final DeliveryService deliveryService;
public OrderFacade(InventoryService inventoryService,
PaymentService paymentService,
DeliveryService deliveryService) {
this.inventoryService = inventoryService;
this.paymentService = paymentService;
this.deliveryService = deliveryService;
}
/**
* Создать заказ.
*/
public void place(OrderDetails orderDetails) {
// Резервируем товары из заказа на складе
inventoryService.reserveItems(orderDetails.items());
// Оплачиваем заказ
paymentService.pay(orderDetails.paymentDetails());
// Передаём заказ в доставку
deliveryService.transferForDelivery(orderDetails.deliveryDetails());
}
/**
* Отменить заказ.
*/
public void cancel(OrderDetails orderDetails) {
// Отменяем резервацию товаров на складе
inventoryService.cancelReservation(orderDetails.items());
// Возвращаем деньги клиенту
paymentService.refund(orderDetails.paymentDetails());
// Отменяем доставку
deliveryService.cancelDelivery(orderDetails.deliveryDetails());
}
}javapackage ecommerce;
import java.util.List;
public record OrderDetails(
List<Item> items,
PaymentDetails paymentDetails,
DeliveryDetails deliveryDetails
) {}javaДля того чтобы создать заказ, нужно:
- Зарезервировать на складе товары из заказа.
- Получить оплату за заказ.
- Передать заказ в доставку.
Для того чтобы отменить заказ, нужно:
- Отменить резервацию товаров на складе.
- Вернуть покупателю деньги.
- Отменить доставку заказа.
Как видно, в операции создания и отмены заказа участвуют 3 компонента (или подсистемы):
- Inventory – сервис склада, для резервирования товаров.
- Payment – сервис оплаты заказа.
- Delivery – сервис доставки заказа.
Класс OrderFacade выполняет функцию фасада, предоставляя пользователю удобный интерфейс для работы с заказом. Пользователю не нужно знать о компонентах InventoryService, PaymentService и DeliveryService. Для того чтобы создать заказ, достаточно такого вызова:
orderFacade.place(orderDetails);javaА для отмены заказа:
orderFacade.cancel(orderDetails);javaРеальные примеры
EntityManager из Hibernate
Hibernate ↗ – это Data Mapper ORM для Java. Он маппит объекты на таблицы базы данных и реализует спецификацию JPA (Jakarta Persistence API).
JPA-интерфейс EntityManager предоставляет фасад для работы с базой данных через ORM. Методы persist() и find() скрывают сложную цепочку действий: получение JDBC-соединения из пула, генерацию SQL из маппингов сущностей, управление кэшем первого уровня и координацию транзакций.
Клиентскому коду не нужно знать обо всех этих подсистемах – достаточно простых вызовов:
// EntityManager – фасад: скрывает JDBC, генерацию SQL,
// кэширование и управление транзакциями
Order order = new Order(items, paymentDetails, deliveryDetails);
em.persist(order);
Order found = em.find(Order.class, 42L);javaЗа вызовом persist() стоит целая подсистема: ConnectionProvider выдаёт JDBC-соединение из пула, ActionQueue определяет порядок выполнения операций при flush, PersistenceContext управляет кэшем первого уровня и отслеживает изменения (dirty checking), а TransactionCoordinator координирует фиксацию транзакции. Метод persist() скрывает всё это за одним вызовом.
database/sql
Пакет database/sql из стандартной библиотеки Go предоставляет фасад для работы с базами данных. Тип sql.DB скрывает управление пулом соединений, взаимодействие с драйвером базы данных и подготовку запросов.
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq" // Драйвер PostgreSQL
)
func main() {
// sql.Open – фасад: скрывает выбор драйвера, создание пула соединений
// и управление их жизненным циклом
db, err := sql.Open("postgres", "host=localhost dbname=shop sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// db.QueryRow – фасад: скрывает получение соединения из пула,
// подготовку запроса и возврат соединения в пул
var name string
err = db.QueryRow("SELECT name FROM products WHERE id = $1", 42).Scan(&name)
if err != nil {
log.Fatal(err)
}
fmt.Println(name)
}goЗа вызовами QueryRow(), Exec() и Begin() стоят подсистемы: пул соединений, менеджер драйверов (driver.Driver) и подготовка запросов (driver.Stmt). Тип sql.DB скрывает всю эту сложность и предоставляет клиентскому коду простой интерфейс.