一、概念
Lazy Load:一個對象,它雖然不包含所需要的所有數據,但是知道怎麼獲取這些數據。
延遲載入似乎很簡單,就是在資料需要時再從資料庫獲取,減少資料庫的消耗。但這其中還是有不少技巧的。
二、實作延遲載入
實現Lazy Load主要有四種方法:延遲初始化、虛代理、值保持器和重影。
(1)延遲初始化(Lazy initialization)
1.1 概念
這個是最簡單的方法。意思是每次存取屬性域都要先檢查該域是否為空,如果為空,再取得這個域的值。這樣必須做到所有對該域的訪問,即使來自類別的內部,都要透過獲取方法來實現。
class Supplier{ private $products; public function getProducts(){ if($products == null) $products = $Product->findForSupplier(); return $products; } }
1.3 使用時機
只有在網域需要另外的資料庫存取時才考慮使用延遲載入。
需要額外的調用,並且當使用主對象時所調用的數據沒有用到的時候。
最適用於活動記錄、表格資料入口和行資料入口。
(2)虛代理(virtual proxy)
2.1 概念
實質為一對象,不包含任何東西,只有當它的一個方法被調用時,它才從數據庫加載恰當的對象。
說簡單點說是一個對象的代理對象,初始化時不載入對象,只有當代理對像被呼叫方法時才真正去載入。
/** * 虚代理,只有在被访问成员时才调用闭包函数生成目标对象。 */ 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。當每次要存取某個網域時,它就會載入其完全狀態。把重影看成一個對象,它的每個域都是被一下子延遲初始化的,或是把它看成一個虛代理,而對象本身就是它的虛代理。
//继承要加载的对象 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)的程式設計。可以將延遲載入操作置於一個單獨的方面,這樣能獨立改變延遲載入策略,領域開發者就不必處理延遲載入的問題了。
無論你是否在領域類別中明確地添加延遲載入程式碼,延遲載入都是一個好習慣。除了型別安全之外,使用集合物件而非陣列的好處是你可以使用延遲載入。