首頁  >  文章  >  後端開發  >  物件關係行為模式之延遲加載

物件關係行為模式之延遲加載

巴扎黑
巴扎黑原創
2016-11-21 10:17:351059瀏覽

一、概念

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)的程式設計。可以將延遲載入操作置於一個單獨的方面,這樣能獨立改變延遲載入策略,領域開發者就不必處理延遲載入的問題了。

無論你是否在領域類別中明確地添加延遲載入程式碼,延遲載入都是一個好習慣。除了型別安全之外,使用集合物件而非陣列的好處是你可以使用延遲載入。


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn