一、概念
Lazy Load:一個對象,它雖然不包含所需要的所有資料,但是知道怎麼擷取這些資料。
消極式載入貌似很簡單,就是在資料需要時再從資料庫擷取,減少資料庫的消耗。但這其中還是有不少技巧的。
二、實現消極式載入
實現Lazy Load主要有四種方法:延遲初始化、虛代理、值保持器和重影。
(1)延遲初始化(Lazy initialization)
1.1 概念
這個是最簡單的方法。意思就是每次訪問屬性域都要先檢查該域是否為空白,如果為空白,再擷取這個域的值。這樣必須做到所有對該域的訪問,即使來自類的內部,都要通過擷取方法來實現。
1.2 代碼實現
class Supplier{ private $products; public function getProducts(){ if($products == null) $products = $Product->findForSupplier(); return $products; }}
1.3 使用時機
只有在域需要另外的資料庫訪問時才考慮使用消極式載入。
需要額外的調用,並且當使用主對象時所調用的資料沒有用到的時候。
最適用於活動記錄、表資料入口和行資料入口。
(2)虛代理(virtual proxy)
2.1 概念
實質為一對象,不包含任何東西,只有當它的一個方法被調用時,它才從資料庫載入恰當的對象。
說簡單點說是一個對象的代理對象,初始化時不載入對象,只有當代理對象被呼叫者法時才真正去載入。
2.2 代碼實現
/** * 虛代理,只有在被訪問成員時才調用閉包函數產生目標對象。 */class VirtualProxy{ private $holder = null; private $loader = null; /** * @param Closure $loader 產生被代理對象的閉包函數 */ public function __construct(Closure $loader) { $this->loader = $loader; } /** * 代理成員方法的調用 * * @param string $method * @param array $arguments * @throws BadMethodCallException * @return mixed */ public function __call($method, array $arguments = null) { $this->check(); if (!method_exists($this->holder, $method)) { throw new BadMethodCallException(); } return call_user_func_array( array(&$this->holder, $method), $arguments); } /** * 代理成員屬性的讀取 * * @param string $property * @throws ErrorException * @return mixed */ public function __get($property) { $this->check(); if (!isset($this->holder->$property)) { throw new ErrorException(); } return $this->holder->$property; } /** * 代理成員屬性的賦值 * * @param string $property * @param mixed $value */ public function __set($property, $value) { $this->check(); $this->holder->$property = $value; } /** * 檢查是否已經存在被代理對象,不存在則產生。 */ private function check() { if (null == $this->holder) { $loader = $this->loader; $this->holder = $loader(); } }}// 測試$v = new VirtualProxy(function(){ echo 'Now, Loading', "\n"; $a = new ArrayObject(range(1,100)); $a->abc = 'a'; // 實際使用中,這裡調用的是 DataMapper 的 findXXX 方法 // 返回的是領域對象集合 return $a;});// 代理對象直接當作原對象訪問// 而此時構造方法傳入的 callback 函數才被調用// 從而實現載入對象操作的延遲echo $v->abc . $v->offsetGet(50);
(3)值保持器(value holder)
3.1 概念
一個用來封裝某個其他對象的對象,要想擷取基底物件,可以訪問值保持器得到它的值,但是只有第一次訪問值保持器時它真正從資料庫讀取資料。
(4)重影(ghost)
4.1 概念
部分狀態下的真實對象,當從資料庫載入對象的時候,它只包含其ID。當每次要訪問某個域時,它就會載入其完全狀態。把重影看成一個對象,它的每個域都是被一下子延遲初始化的,或者把它看成一個虛代理,對象本身就是它的虛代理。
4.2 代碼實現
//繼承要載入的對象class DeferredEventCollection extends EventCollection { private $stmt; private $valueArray; private $run=false;//標識當前載入狀態 //構造方法,不真正擷取資料,只包含其$valueArray(ID) function __construct( Mapper $mapper, \PDOStatement $stmt_handle,array $valueArray ) { parent::__construct( null, $mapper ); $this->stmt = $stmt_handle; $this->valueArray = $valueArray; } //載入完全狀態 function notifyAccess() { if ( ! $this->run ) { $this->stmt->execute( $this->valueArray ); $this->raw = $this->stmt->fetchAll(); $this->total = count( $this->raw ); } $this->run=true; }}
三、消極式載入的風險
1、繼承常常會給消極式載入帶來問題。如果要用重影,需要知道要建立什麼類型的重影,如果沒有正確載入資料,往往就很難說了。虛代理在待用資料類型語言中也會遇到同樣的問題。
2、消極式載入容易導致產生超出需要的資料庫訪問。書中別名“波動載入”。如用消極式載入來填充一個集合,然後每次只訪問其中一個元素。這樣就會每訪問一個對象就訪問一次資料庫,而不是一次把所需對象全部讀出來。這種載入方式會嚴重影響系統的效能。當然,可以不使用消極式載入的集合,而使類集合本身成為消極式載入,載入類集合時一次載入全部內容。
四、小結
消極式載入很適合於面向方面(AOP)的程式設計。可以將消極式載入操作置於一個單獨的方面,這樣能獨立改變消極式載入策略,領域開發人員也不必處理消極式載入的問題了。
無論你是否在領域類中顯式地添加消極式載入代碼,消極式載入都是一個好習慣。除了型別安全之外,使用集合對象而非數組的好處是你可以使用消極式載入。