Lazy Load (Ленивая загрузка)

Паттерн проектирования Lazy Load

Паттерн проектирования Lazy Load

Описание Lazy Load

Объект, не содержит данных, но знает, где их взять.

Для загрузки данных из БД в память приложения удобно пользоваться загрузкой не только данных об объекте, но и о сопряжённых с ним объектах. Это делает загрузку данных проще для разработчика: он просто использует объект, который, тем не менее вынужден загружать все данные в явном виде.

Но это ведёт к случаям, когда будет загружаться огромное количество сопряжённых объектов, что плохо скажется на производительности в случаях, когда эти данные реально не нужны.

Паттерн Lazy Load (Ленивая Загрузка) подразумевает отказ от загрузки дополнительных данных, когда в этом нет необходимости. Вместо этого ставится маркер о том, что данные не загружены и их надо загрузить в случае, если они понадобятся. Как известно, если Вы ленивы, то вы выигрываете в том случае, если дело, которое вы не делали на самом деле и не надо было делать.

Существует четыре основных варианта ленивой загрузки.

Примеры реализации

// 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
?>

Использована иллюстрация с сайта Мартина Фаулера.

Источник