Lazy Load (Ленивая загрузка)
Паттерн проектирования Lazy Load
Описание Lazy Load
Объект, не содержит данных, но знает, где их взять.
Для загрузки данных из БД в память приложения удобно пользоваться загрузкой не только данных об объекте, но и о сопряжённых с ним объектах. Это делает загрузку данных проще для разработчика: он просто использует объект, который, тем не менее вынужден загружать все данные в явном виде.
Но это ведёт к случаям, когда будет загружаться огромное количество сопряжённых объектов, что плохо скажется на производительности в случаях, когда эти данные реально не нужны.
Паттерн Lazy Load (Ленивая Загрузка) подразумевает отказ от загрузки дополнительных данных, когда в этом нет необходимости. Вместо этого ставится маркер о том, что данные не загружены и их надо загрузить в случае, если они понадобятся. Как известно, если Вы ленивы, то вы выигрываете в том случае, если дело, которое вы не делали на самом деле и не надо было делать.
Существует четыре основных варианта ленивой загрузки.
- Lazy Initialization (Ленивая Инициализация) использует специальный макер (обычно null), чтобы пометить поле, как не загруженное. При каждом обращении к полю проверяется значение маркера и, если значение поля не загружено - оно загружается.
- Virtual Proxy (Виртуальный Прокси) - объект с таким же интерфейсом, как и настоящий объект. При первом обращении к методу объекта, виртуальный прокси загружает настоящий объект и перенаправляет выполнение.
- Value Holder (Контейнер значения) - объект с методом getValue. Клиент вызывает метод getValue, чтобы получить реальный объект. getValue вызывает загрузку.
- Ghost (Призрак) - объект без каких-либо данных. При первом обращении к его методу, призрак загружает все данные сразу.
Примеры реализации
// Lazy Load Pattern in JavaScript
class User {
constructor(id, name) {
this.id = id;
this.name = name;
this._orders = null; // Lazy loaded
}
get orders() {
if (this._orders === null) {
this._orders = this.loadOrders();
}
return this._orders;
}
loadOrders() {
console.log(`Loading orders for user ${this.id}`);
return [
{ id: 1, amount: 100 },
{ id: 2, amount: 200 }
];
}
}
// Virtual Proxy
class OrderProxy {
constructor(orderId) {
this.orderId = orderId;
this._order = null;
}
get amount() {
if (this._order === null) {
this._order = this.loadOrder();
}
return this._order.amount;
}
loadOrder() {
console.log(`Loading order ${this.orderId}`);
return { id: this.orderId, amount: 150 };
}
}
// Usage
const user = new User(1, 'John');
console.log(user.name); // No lazy loading
console.log(user.orders); // Triggers lazy loading
const orderProxy = new OrderProxy(1);
console.log(orderProxy.amount); // Triggers lazy loading
// Lazy Load Pattern in C++
#include <iostream>
#include <vector>
#include <memory>
class Order {
public:
int id;
double amount;
Order(int id, double amount) : id(id), amount(amount) {}
};
class User {
private:
int id;
std::string name;
mutable std::vector<Order>* orders;
public:
User(int id, const std::string& name) : id(id), name(name), orders(nullptr) {}
const std::vector<Order>& getOrders() const {
if (orders == nullptr) {
orders = new std::vector<Order>();
loadOrders();
}
return *orders;
}
private:
void loadOrders() const {
std::cout << "Loading orders for user " << id << std::endl;
orders->push_back(Order(1, 100.0));
orders->push_back(Order(2, 200.0));
}
};
// Virtual Proxy
class OrderProxy {
private:
int orderId;
mutable Order* order;
public:
OrderProxy(int id) : orderId(id), order(nullptr) {}
double getAmount() const {
if (order == nullptr) {
loadOrder();
}
return order->amount;
}
private:
void loadOrder() const {
std::cout << "Loading order " << orderId << std::endl;
order = new Order(orderId, 150.0);
}
};
// Lazy Load Pattern in Go
package main
import "fmt"
type Order struct {
ID int
Amount float64
}
type User struct {
ID int
Name string
orders []Order
loaded bool
}
func NewUser(id int, name string) *User {
return &User{ID: id, Name: name, loaded: false}
}
func (u *User) GetOrders() []Order {
if !u.loaded {
u.loadOrders()
u.loaded = true
}
return u.orders
}
func (u *User) loadOrders() {
fmt.Printf("Loading orders for user %d\n", u.ID)
u.orders = []Order{
{ID: 1, Amount: 100},
{ID: 2, Amount: 200},
}
}
// Virtual Proxy
type OrderProxy struct {
orderID int
order *Order
}
func NewOrderProxy(orderID int) *OrderProxy {
return &OrderProxy{orderID: orderID}
}
func (op *OrderProxy) GetAmount() float64 {
if op.order == nil {
op.loadOrder()
}
return op.order.Amount
}
func (op *OrderProxy) loadOrder() {
fmt.Printf("Loading order %d\n", op.orderID)
op.order = &Order{ID: op.orderID, Amount: 150}
}
// Usage
func main() {
user := NewUser(1, "John")
fmt.Println(user.Name) // No lazy loading
orders := user.GetOrders() // Triggers lazy loading
fmt.Println(orders)
orderProxy := NewOrderProxy(1)
amount := orderProxy.GetAmount() // Triggers lazy loading
fmt.Println(amount)
}
# Lazy Load Pattern in Python
class Order:
def __init__(self, order_id, amount):
self.id = order_id
self.amount = amount
class User:
def __init__(self, user_id, name):
self.id = user_id
self.name = name
self._orders = None
@property
def orders(self):
if self._orders is None:
self._orders = self._load_orders()
return self._orders
def _load_orders(self):
print(f"Loading orders for user {self.id}")
return [
Order(1, 100),
Order(2, 200)
]
# Virtual Proxy
class OrderProxy:
def __init__(self, order_id):
self.order_id = order_id
self._order = None
@property
def amount(self):
if self._order is None:
self._order = self._load_order()
return self._order.amount
def _load_order(self):
print(f"Loading order {self.order_id}")
return Order(self.order_id, 150)
# Usage
if __name__ == "__main__":
user = User(1, "John")
print(user.name) # No lazy loading
print(user.orders) # Triggers lazy loading
order_proxy = OrderProxy(1)
print(order_proxy.amount) # Triggers lazy loading
<?php
// Lazy Load Pattern in PHP
class Order {
public $id;
public $amount;
public function __construct($id, $amount) {
$this->id = $id;
$this->amount = $amount;
}
}
class User {
private $id;
private $name;
private $orders;
public function __construct($id, $name) {
$this->id = $id;
$this->name = $name;
$this->orders = null;
}
public function getOrders() {
if ($this->orders === null) {
$this->orders = $this->loadOrders();
}
return $this->orders;
}
private function loadOrders() {
echo "Loading orders for user {$this->id}\n";
return [
new Order(1, 100),
new Order(2, 200)
];
}
}
// Virtual Proxy
class OrderProxy {
private $orderId;
private $order;
public function __construct($orderId) {
$this->orderId = $orderId;
$this->order = null;
}
public function getAmount() {
if ($this->order === null) {
$this->order = $this->loadOrder();
}
return $this->order->amount;
}
private function loadOrder() {
echo "Loading order {$this->orderId}\n";
return new Order($this->orderId, 150);
}
}
// Usage
$user = new User(1, "John");
echo $user->name . "\n"; // No lazy loading
$orders = $user->getOrders(); // Triggers lazy loading
$orderProxy = new OrderProxy(1);
$amount = $orderProxy->getAmount(); // Triggers lazy loading
?>
Использована иллюстрация с сайта Мартина Фаулера.