Реализация загрузки по требованию

Jump to: navigation, search

с помощью виртуальных прокси-объектов и средств PHP

Автор — Геннадий Круглов.

По мере освоения программистами объектно-ориентированной парадигмы для отображения бизнес-логики все чаще используется шаблон проектирования Domain Model (модель предметной области)[1]. Широко известны две разновидности Domain Model: простая и сложная. Простая, по сути, является объектным отображением реляционной схемы и содержит по одному объекту предметной области на каждую таблицу. При ее использовании первичной является реляционная модель. Сложная модель более адекватно отображает сложную бизнес-логику и, как правило, содержит иерархии наследования и множество взаимосвязанных объектов. При использовании сложной модели предметной области и реляционной базы данных для хранения объектов, что на сегодняшний день очень актуально, востребованным становится объектно-реляционное отображение (ORM — object-relational mapping), т. к. на первый план при проектировании приложения выходит объектно-ориентированная модель и поэтому разработчик уже не оперирует терминами таблиц и внешних ключей.

При загрузке объекта предметной области из реляционной базы данных необходимо обеспечить автоматическую загрузку связанных с ним объектов. В противном случае придется прописывать загрузку всех объектов вручную, что приведет к «размазыванию» бизнес-логики из-за дополнительных зависимостей от базы данных.

Автоматическая загрузка всей структуры взаимосвязанных объектов позволяет существенно упростить программирование, но в то же время может привести к нежелательным последствиям: нерациональному использованию ресурсов, например загрузка глубокой иерархии объектов, когда требуется лишь только ее часть; зацикливанию программы при наличии двунаправленной ассоциации между объектами.

Нежелательных последствий можно избежать, если использовать шаблон проектирования Lazy Load (загрузка по требованию)[1]. Загрузка по требованию позволяет прерывать процесс загрузки и загружать нужные объекты только тогда, когда это действительно необходимо.

Существует несколько способов реализации Lazy Load. Один из таких способов описан в шаблоне проектирования Virtual Proxy (виртуальный прокси-объект)[2][3].

Virtual Proxy имитирует объект предметной области, однако в действительности содержит только данные необходимые для загрузки реального объекта. При этом загрузка реального объекта произойдет только тогда, когда будет вызван один из методов виртуального прокси-объекта.

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

PHP является динамически типизированным и позволяет более эффективно реализовать шаблон Virtual Proxy. Кроме того, PHP поддерживает механизм перехвата обращений к свойствам и методам. В основе этого механизма лежат полномочные методы[4] __get(), __set() и __call(), вызываемые автоматически, если соответствующие метод или поле не определены в классе. Полномочный метод __call() позволяет существенно упростить создание единственного класса Virtual Proxy для всех классов предметной области. Экземпляры такого класса могут выступать в роли объектов-посредников для любых объектов предметной области.

class VirtualProxy {
    private $real_class_name = ''; // класс реального объекта
    private $id = ''; // идентификатор объекта предметной области
    private $real_object = null; // ссылка на реальный объект
    
    function __construct($real_class_name = '', $id = '') {
        $this->real_class_name = $real_class_name;
        $this->id = $id;
    }
    
    public function getRealClassName() {
        return $this->real_class_name;
    }
    
    public function setRealClassName($value = '') {
        $this->real_class_name = $value;
    }
    
    public function getId() {
        return $this->id;
    }
    
    public function setId($value = '') {
        $this->id = $value;
    }
    
    function __call($method_name, $args) {
        if (empty($this->real_object)) {
            // если объект предметной области не загружен, то происходит его загрузка из БД
            // в данном примере используется код ORM CMS “Blackbox”
            $this->real_object = uow::getInstance()->execute($this->real_class_name,
                'findById', array('id' => $this->id));
        }
        if (!empty($this->real_object)) {
            $method = new ReflectionMethod(get_class($this->real_object), $method_name);
            if ($method->isPublic() && !$method->isAbstract()) {
                /* если реальный объект загружен, и затребованный метод может быть вызван,
                   Virtual Proxy делегирует вызов метода реальному объекту */
                return $method->invoke($this->real_object, $args);
            }
        }
    }
}

В листинге метод __call() определяет, загружен ли объект предметной области и если не загружен, то загружает его из базы данных и сохраняет в поле $real_object. Затем __call() с помощью механизма рефлексии определяет, является ли затребованный метод открытым и конкретным, и в случае успеха делегирует объекту предметной области вызов затребованного метода.

Теперь для создания виртуального прокси-объекта любого класса предметной области достаточно лишь вызвать конструктор и инициализировать его значениями идентификатора и имени класса реального объекта.

Литература

  1. 1.0 1.1 Мартин Фаулер Архитектура корпоративных программных приложений. — М.: «Вильямс», 2007.
  2. Крэг Ларман Применение UML и шаблонов проектирования. — М.: «Вильямс», 2002.
  3. Марк Гранд Шаблоны проектирования в Java. — СПб.: «Символ-Плюс», 2004.
  4. Энди Гутманс, Стиг Баккен, Дерик Ретанс PHP 5. Профессиональное программирование. — СПб.: «Символ-Плюс», 2006.
Personal tools
ссылка