Паттерн проектирования Прокси (Proxy) на PHP 21.08.2011

Перед прочтением ознакомьтесь с введением в паттерны проектирования на PHP, в котором описаны принятые соглашения и понятия. Данная статья дополняется с некоторой периодичностью, так что если вы ее читали ранее, не факт что данные не изменились.

Прокси (Proxy, Заместитель) относиться к классу структурных паттернов. Является суррогатом другого объекта и контролирует доступ к нему.

Наиболее частым применением паттерна прокси является ленивая загрузка (lazy load). «Тяжелые» объекты не всегда разумно загружать в момент инициализации. Более правильным решением будет загрузить его по первому требованию. Давайте рассмотрим это на примере некой обертки для удаленных (расположенных удаленно) файлов. Информацию о самом файле мы будем хранить в базе данных, а сам файл где-то на другом сервере.

  1.  
  2. class RemoteFile
  3. {
  4.     protected $_fileId0;
  5.     protected $_filepath"";
  6.     protected $_filesize0;
  7.     protected $_filename"";
  8.     protected $_filedatanull;
  9.  
  10.     /**
  11.      * Load file by file id
  12.      *
  13.      * @param int $fileId
  14.      */
  15.     public function loadById($fileId)
  16.     {
  17.         $this -> _fileId = $fileId;
  18.         $this -> _loadFromDatabase($fileId);
  19.         $this -> _filedata = file_get_contents($this -> _filepath);
  20.     }
  21.  
  22.     /**
  23.      * некоторый код для загрузки информации о файле из БД
  24.      *
  25.      * @param int $fileId
  26.      */
  27.     public function _loadFromDatabase($fileId)
  28.     {
  29.         $fileinfo = DbAdapter::loadFileInfo($fileId);
  30.  
  31.         $this -> _filepath = $fileinfo['path'];
  32.         $this -> _filesize = $fileinfo['size'];
  33.         $this -> _filename = $fileinfo['name'];
  34.     }
  35.  
  36.     /**
  37.      * @return int
  38.      */
  39.     public function getFileId()
  40.     {
  41.         return $this -> _fileId;
  42.     }
  43.  
  44.     /**
  45.      * @return string
  46.      */
  47.     public function getFileContents()
  48.     {
  49.         return $this -> _filedata;
  50.     }
  51.  
  52.     /**
  53.      * @return int
  54.      */
  55.     public function getFileSize()
  56.     {
  57.         return $this -> _filesize;
  58.     }
  59.  
  60.     /**
  61.      * @return string
  62.      */
  63.     public function getFileName()
  64.     {
  65.         return $this -> _filename;
  66.     }
  67. }
  68.  
  69. // используем как-то так
  70.  
  71. $file = new RemoteFile();
  72. $file -> loadById(1);
  73.  
  74. var_dump( $file -> getFileSize() );
  75.  

В виде файла

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

  1.  
  2. // Наш прокси будет наследовать исходный класс и, как следствие, реализовывать
  3. // его интерфейс.
  4. class RemoteFileProxy extends RemoteFile
  5. {
  6.     /**
  7.      * Load file by file id
  8.      *
  9.      * @param int $fileId
  10.      */
  11.     public function loadById($fileId)
  12.     {
  13.         // Мы загружаем информацию только из БД, а сам файл не грузим
  14.         $this -> _loadFromDatabase($fileId);
  15.     }
  16.  
  17.     /**
  18.      * @return string
  19.      */
  20.     public function getFileContents()
  21.     {
  22.         if (null === $this -> _filedata) {
  23.             $this -> _filedata = file_get_contents($this -> _filepath);
  24.         }
  25.         
  26.         return $this -> _filedata;
  27.     }
  28. }
  29.  

В виде файла

Теперь файл не загружается при инициализации, при этом по первому требованию «getFileContents» файл будет отдан. Так как прокси реализует интерфейс исходного класса, мы можем безболезненно подменять оригинальный класс везде, где это нужно. Однако это все еще не полностью паттерн прокси. Его структура в общем виде выглядит так:

UML

Попробуем усовершенствовать наш прокси так, чтобы он загружал данные из базы данных только по запросу:

  1. class RemoteFileExtendedProxy extends RemoteFileProxy
  2. {
  3.     /**
  4.      * @var RemoteFile
  5.      */
  6.     protected $_realRemoteFilenull;
  7.  
  8.     /**
  9.      * @var int
  10.      */
  11.     protected $_fileId0;
  12.  
  13.     /**
  14.      * Load file by file id
  15.      *
  16.      * @param int $fileId
  17.      */
  18.     public function loadById($fileId)
  19.     {
  20.         $this -> _fileId = $fileId;
  21.     }
  22.  
  23.     public function getFileId()
  24.     {
  25.         return $this -> _getRealRemoteFile() -> getFileId();
  26.     }
  27.  
  28.     public function getFileName()
  29.     {
  30.         return $this -> _getRealRemoteFile() -> getFileName();
  31.     }
  32.  
  33.     public function getFileSize()
  34.     {
  35.         return $this -> _getRealRemoteFile() -> getFileSize();
  36.     }
  37.  
  38.     public function getFileContents()
  39.     {
  40.         return $this -> _getRealRemoteFile() -> getFileContents();
  41.     }
  42.  
  43.     /**
  44.      * @return RemoteFileProxy
  45.      */
  46.     public function _getRealRemoteFile()
  47.     {
  48.         if (null == $this -> _realRemoteFile) {
  49.             $this -> _realRemoteFile = new RemoteFileProxy();
  50.             $this -> _realRemoteFile -> loadById($this -> _fileId);
  51.         }
  52.  
  53.         return $this -> _realRemoteFile;
  54.     }
  55. }
  56.  

В виде файла

Таким образом, при вызове getFileId мы получим id сразу, при вызове getFileSize, getFileName и getFileContents сначала создадим объект RemoteFileProxy и делегируем выполнения ему. В свою очередь RemoteFileProxy будет стараться отложить загрузку файла до последнего. 

Частые применения

  • При использовании подхода DDD (Domain-driven design) при проектировании, для связки модели с маппером можно использовать прокси
  • Можно использовать прокси, как локальных представителей распределенных объектов

, , , ,


Похожие статьи


  1. IAD:

    Ещё способ применения: делаем базовый класс абстрактным смысловым ядром (допустим CRUD для абстрактной таблицы БД).

    В наследуемом классе (extend) делаем определение названия таблицы, для которой методы базового класса будут реализовать этот CRUD. Ну так вот, в наследуемом классе можно реализовать фильтры выполняя проверку, а выполнения передавая методу базового класса, таким образом:

    function get_all ($sort=array ('url'=>'1'))

    {

    return (parent::get_all ($sort));

    }

    Но это уже на этапе выделения абстрактного смыслового ядра.

Добавить комментарий