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

形象化设计模式实战     HELLO!架构

 

一、概念

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)的程序设计。可以将延迟加载操作置于一个单独的方面,这样能独立改变延迟加载策略,领域开发者也不必处理延迟加载的问题了。

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
单例模式延迟加载指的是在需要使用单例对象时才进行初始化,而不是在程序启动时就创建实例。这种延迟加载的好处是可以节省内存资源,在某些情况下可以提高程序的性能。 然而,延迟加载的单例模式在多线程环境下可能会引发线程安全问题,主要有以下两种情况: 1. 线程安全问题:在多线程同时访问getInstance()方法时,可能会创建多个实例。例如,当两个线程同时检查到实例为null时,都会创建一个新的实例,导致不再是单例模式。 2. 性能问题:当多个线程都想要获取单例实例时,如果没有合适的同步机制,每个线程都会创建一个新的实例,增加了系统开销。 为了解决延迟加载的线程安全问题,可以采用双重检查锁定(Double-Checked Locking)机制。简单来说,就是在getInstance()方法中进行两次检查,第一次检查是否为空,第二次检查使用synchronized关键字进行同步。 下面是一个使用双重检查锁定实现延迟加载的示例代码: ```java public class Singleton { private static volatile Singleton instance; private Singleton() { // 私有构造函数 } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } ``` 在上述代码中,使用了volatile关键字来保证instance变量的可见性,避免指令重排带来的问题。 总之,延迟加载的单例模式可以提高程序性能和节省内存资源,但需要注意线程安全问题,可以采用双重检查锁定机制来解决。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值