一、概念

Lazy Load:一个对象,它虽然不包含所需要的所有数据,但是知道怎么获取这些数据。

延迟加载貌似很简单,就是在数据需要时再从数据库获取,减少数据库的消耗。但这其中还是有不少技巧的。

 

 

二、实现延迟加载

实现Lazy Load主要有四种方法:延迟初始化、虚代理、值保持器和重影。

 

(1)延迟初始化(Lazy initialization)

 

1.1 概念

这个是最简单的方法。意思就是每次访问属性域都要先检查该域是否为空,如果为空,再获取这个域的值。这样必须做到所有对该域的访问,即使来自类的内部,都要通过获取方法来实现。

 

1.2 代码实现

Php代码  收藏代码
  1. class Supplier{
  2.   private $products;
  3.   public function getProducts(){
  4.     if($products == null)
  5.       $products = $Product->findForSupplier();
  6.     return $products;
  7.   }
  8. }

 

1.3 使用时机

只有在域需要另外的数据库访问时才考虑使用延迟加载。

需要额外的调用,并且当使用主对象时所调用的数据没有用到的时候。

最适用于活动记录、表数据入口和行数据入口。

 

 

(2)虚代理(virtual proxy)

 

2.1 概念

实质为一对象,不包含任何东西,只有当它的一个方法被调用时,它才从数据库加载恰当的对象。

说简单点说是一个对象的代理对象,初始化时不加载对象,只有当代理对象被调用方法时才真正去加载。

 

2.2 代码实现

Php代码  收藏代码
  1. /**
  2.  * 虚代理,只有在被访问成员时才调用闭包函数生成目标对象。
  3.  */
  4. class VirtualProxy
  5. {
  6.     private $holder = null;
  7.     private $loader = null;
  8.     /**
  9.      * @param Closure $loader 生成被代理对象的闭包函数
  10.      */
  11.     public function __construct(Closure $loader)
  12.     {
  13.         $this->loader = $loader;
  14.     }
  15.     /**
  16.      * 代理成员方法的调用
  17.      *
  18.      * @param string $method
  19.      * @param array  $arguments
  20.      * @throws BadMethodCallException
  21.      * @return mixed
  22.      */
  23.     public function __call($method, array $arguments = null)
  24.     {
  25.         $this->check();
  26.         if (!method_exists($this->holder, $method)) {
  27.             throw new BadMethodCallException();
  28.         }
  29.         return call_user_func_array(
  30.             array(&$this->holder, $method),
  31.             $arguments);
  32.     }
  33.     /**
  34.      * 代理成员属性的读取
  35.      *
  36.      * @param string $property
  37.      * @throws ErrorException
  38.      * @return mixed
  39.      */
  40.     public function __get($property)
  41.     {
  42.         $this->check();
  43.         if (!isset($this->holder->$property)) {
  44.             throw new ErrorException();
  45.         }
  46.         return $this->holder->$property;
  47.     }
  48.     /**
  49.      * 代理成员属性的赋值
  50.      *
  51.      * @param string $property
  52.      * @param mixed  $value
  53.      */
  54.     public function __set($property, $value)
  55.     {
  56.         $this->check();
  57.         $this->holder->$property = $value;
  58.     }
  59.     /**
  60.      * 检查是否已经存在被代理对象,不存在则生成。
  61.      */
  62.     private function check()
  63.     {
  64.         if (null == $this->holder) {
  65.             $loader = $this->loader;
  66.             $this->holder = $loader();
  67.         }
  68.     }
  69. }
  70. // 测试
  71. $v = new VirtualProxy(function(){
  72.         echo 'Now, Loading', "\n";
  73.         $a = new ArrayObject(range(1,100));
  74.         $a->abc = 'a';
  75.         // 实际使用中,这里调用的是 DataMapper 的 findXXX 方法
  76.         // 返回的是领域对象集合
  77.         return $a;
  78. });
  79. // 代理对象直接当作原对象访问
  80. // 而此时构造方法传入的 callback 函数才被调用
  81. // 从而实现加载对象操作的延迟
  82. echo $v->abc . $v->offsetGet(50);

 

 (3)值保持器(value holder)

 

3.1 概念

一个用来包装某个其他对象的对象,要想获取基对象,可以访问值保持器得到它的值,但是只有第一次访问值保持器时它真正从数据库读取数据。

 

 (4)重影(ghost)

 

4.1 概念

部分状态下的真实对象,当从数据库加载对象的时候,它只包含其ID。当每次要访问某个域时,它就会加载其完全状态。把重影看成一个对象,它的每个域都是被一下子延迟初始化的,或者把它看成一个虚代理,对象本身就是它的虚代理。

 

4.2 代码实现

Php代码  收藏代码
  1. //继承要加载的对象
  2. class DeferredEventCollection extends EventCollection {
  3.     private $stmt;
  4.     private $valueArray;
  5.     private $run=false;//标识当前加载状态
  6.     //构造方法,不真正获取数据,只包含其$valueArray(ID)
  7.     function __construct( Mapper $mapper, \PDOStatement $stmt_handle,array $valueArray ) {
  8.         parent::__construct( null, $mapper );
  9.         $this->stmt = $stmt_handle;
  10.         $this->valueArray = $valueArray;
  11.     }
  12.     //加载完全状态
  13.     function notifyAccess() {
  14.         if ( ! $this->run ) {
  15.             $this->stmt->execute( $this->valueArray );
  16.             $this->raw = $this->stmt->fetchAll();
  17.             $this->total = count( $this->raw );
  18.         }
  19.         $this->run=true;
  20.     }
  21. }

 

 

三、延迟加载的风险

 

1、继承常常会给延迟加载带来问题。如果要用重影,需要知道要创建什么类型的重影,如果没有正确加载数据,往往就很难说了。虚代理在静态数据类型语言中也会遇到同样的问题。

 

2、延迟加载容易导致产生超出需要的数据库访问。书中别名“波动加载”。如用延迟加载来填充一个集合,然后每次只访问其中一个元素。这样就会每访问一个对象就访问一次数据库,而不是一次把所需对象全部读出来。这种加载方式会严重影响系统的性能。当然,可以不使用延迟加载的集合,而使类集合本身成为延迟加载,加载类集合时一次加载全部内容。

 

 

四、小结

延迟加载很适合于面向方面(AOP)的程序设计。可以将延迟加载操作置于一个单独的方面,这样能独立改变延迟加载策略,领域开发者也不必处理延迟加载的问题了。

无论你是否在领域类中显式地添加延迟加载代码,延迟加载都是一个好习惯。除了类型安全之外,使用集合对象而非数组的好处是你可以使用延迟加载。

对象关系行为模式之延迟加载
标签: