Lazy Loading Pattern

Intent

An object that doesn’t contain all of the data you need but knows how to get it.
This pattern is commonly found in most of the OR mappers, e.g. Hibernate.

Example

When you load the company from company table, you may or may not want to load employees data from the employees table. Using Lazy Load pattern, you load company from company table, and employees data can be loaded when it is needed.

Key Points

  • One object can have the effect of loading a huge number of related objects—something that hurts performance when only a few of the objects are actually needed.
  • Lazy loading is a concept where we delay the loading of an object until the point where we need it.
  • In simple words, Lazy loading is a software design pattern where the initialization of an object occurs only when it is actually needed and not before to preserve the simplicity of usage and improve performance.

Applicability

Use the Lazy Loading idiom when
  • eager loading is expensive or the object to be loaded might not be needed at all

Real-world examples

JPA annotations @OneToOne, @OneToMany, @ManyToOne, @ManyToMany and fetch = FetchType.LAZY

How It Works

There are four main ways we can implement Lazy Load:
  1. lazy initialization
  2. virtual proxy
  3. value holder
  4. ghost

lazy initialization

The Lazy Initialization technique consists of checking the value of a class field when it’s being used. If that value equals null then that field gets loaded with the proper value before it is returned.
The essence of lazy initialization is code like this:
// Java program to illustrate
// Lazy Initialization in
// Lazy Loading Design Pattern
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
 
enum CarType {
    none,
    Audi,
    BMW,
}
 
class Car {
    private static Map<CarType, Car> types = new HashMap<>();
 
    private Car(CarType type) {}
 
    public static Car getCarByTypeName(CarType type)
    {
        Car Car;
 
        if (!types.containsKey(type)) {
            // Lazy initialisation
            Car = new Car(type);
            types.put(type, Car);
        } else {
            // It's available currently
            Car = types.get(type);
        }
 
        return Car;
    }
 
    public static Car getCarByTypeNameHighConcurrentVersion(CarType type)
    {
        if (!types.containsKey(type)) {
            synchronized(types)
            {
                // Check again, after having acquired the lock to make sure
                // the instance was not created meanwhile by another thread
                if (!types.containsKey(type)) {
                    // Lazy initialisation
                    types.put(type, new Car(type));
                }
            }
        }
 
        return types.get(type);
    }
 
    public static void showAll()
    {
        if (types.size() > 0) {
 
            System.out.println("Number of instances made = " + types.size());
 
            for (Entry<CarType, Car> entry : types.entrySet()) {
                String Car = entry.getKey().toString();
                Car = Character.toUpperCase(Car.charAt(0)) + Car.substring(1);
                System.out.println(Car);
            }
 
            System.out.println();
        }
    }
}
 
class Program {
    public static void main(String[] args)
    {
        Car.getCarByTypeName(CarType.BMW);
        Car.showAll();
        Car.getCarByTypeName(CarType.Audi);
        Car.showAll();
        Car.getCarByTypeName(CarType.BMW);
        Car.showAll();
    }
}

Virtual proxy

The Virtual Proxy pattern is a memory saving technique that recommends postponing an object creation until it is needed. It is used when creating an object the is expensive in terms of memory usage or processing involved.
// Java program to illustrate
// virtual proxy in
// Lazy Loading Design Pattern
import java.util.List;
import java.util.ArrayList;
 
interface ContactList
{
public List<Employee> getEmployeeList();
}
 
class Company {
    String companyName;
    String companyAddress;
    String companyContactNo;
    ContactList contactList;
 
    public Company(String companyName, String companyAddress,
            String companyContactNo, ContactList contactList)
    {
        this.companyName = companyName;
        this.companyAddress = companyAddress;
        this.companyContactNo = companyContactNo;
        this.contactList = contactList;
    }
 
    public String getCompanyName()
    {
        return companyName;
    }
    public String getCompanyAddress()
    {
        return companyAddress;
    }
    public String getCompanyContactNo()
    {
        return companyContactNo;
    }
    public ContactList getContactList()
    {
        return contactList;
    }
 
}
 
class ContactListImpl implements ContactList {
    public List<Employee> getEmployeeList()
    {
        return getEmpList();
    }
    private static List<Employee> getEmpList()
    {
        List<Employee> empList = new ArrayList<Employee>(5);
 
        empList.add(new Employee("Lokesh", 2565.55, "SE"));
        empList.add(new Employee("Kushagra", 22574, "Manager"));
        empList.add(new Employee("Susmit", 3256.77, "G4"));
        empList.add(new Employee("Vikram", 4875.54, "SSE"));
        empList.add(new Employee("Achint", 2847.01, "SE"));
 
        return empList;
    }
}
 
class ContactListProxyImpl implements ContactList {
    private ContactList contactList;
    public List<Employee> getEmployeeList()
    {
        if (contactList == null) {
            System.out.println("Fetching list of employees");
            contactList = new ContactListImpl();
        }
        return contactList.getEmployeeList();
    }
}
 
class Employee {
    private String employeeName;
 
    private double employeeSalary;
    private String employeeDesignation;
 
    public Employee(String employeeName,
             double employeeSalary, String employeeDesignation)
    {
        this.employeeName = employeeName;
        this.employeeSalary = employeeSalary;
        this.employeeDesignation = employeeDesignation;
    }
    public String getEmployeeName()
    {
        return employeeName;
    }
    public double getEmployeeSalary()
    {
        return employeeSalary;
    }
    public String getEmployeeDesignation()
    {
        return employeeDesignation;
    }
    public String toString()
    {
        return "Employee Name: " + employeeName + ", 
               EmployeeDesignation : " + employeeDesignation + ",
               Employee Salary : " + employeeSalary;
    }
}
 
class LazyLoading {
    public static void main(String[] args)
    {
        ContactList contactList = new ContactListProxyImpl();
        Company company = new Company
        ("Geeksforgeeks", "India", "+91-011-28458965", contactList);
 
        System.out.println("Company Name: " + company.getCompanyName());
        System.out.println("Company Address: " + company.getCompanyAddress());
        System.out.println("Company Contact No.: " + company.getCompanyContactNo());
        System.out.println("Requesting for contact list");
 
        contactList = company.getContactList();
        List<Employee> empList = contactList.getEmployeeList();
        for (Employee emp : empList) {
            System.out.println(emp);
        }
    }
}
Now, In the above code have a Company object is created with a proxy ContactList object. At this time, the Company object holds a proxy reference, not the real ContactList object’s reference, so there no employee list loaded into the memory.

Value Holder

Basically, A value holder is a generic object that handles the lazy loading behavior and appears in place of the object’s data fields. When the user needs to access it, they simply ask the value holder for its value by calling the GetValue method. At that time (and only then), the value gets loaded from a database or from a service. (this is not always needed).
// Java function to illustrate
// Lazy Initialization in
// Lazy Loading Design Pattern
public class ValueHolder<T> {
    private T value;
    private readonly Func<object, T> valueRetrieval;
 
    // Constructor
    public ValueHolder(Func<object, T> valueRetrieval)
    {
        valueRetrieval = this.valueRetrieval;
    }
 
    // We'll use the signature "GetValue" for convention
    public T GetValue(object parameter)
    {
        if (value == null)
            value = valueRetrieval(parameter);
        return value;
    }
}

Ghost

A "ghost" is the object that is to be loaded in a partial state. It may only contain the object's identifier, but it loads its own data the first time one of its properties is accessed. For example, consider that a user is about to request content via an online form. At the time of creation, all we know is that content will be accessed but what action or content is unknown. PHP Example:
$userData = array(
    "UID" = > uniqid(),
    "requestTime" = > microtime(true),
    "dataType" = > "",
    "request" = > "");
 
if (isset($_POST['data']) && $userData) {
    //...
}

References

https://en.wikipedia.org/wiki/Lazy_loading

Comments