[php]延迟加载和领域对象工厂

        延迟加载

        延迟加载是一个用于避免过多数据库查询的极为重要的机制,大部分web应用都需要注意对大量数据的操作和查询,所以延迟加载还是很有必要学习的。

        在之前我们的建立的数据表和例子可以知道,每个Classroom对象可能包含多个Student对象,而每个Student对象又有可能和更多的Score类进行关联。那么,当从数据库中取出一个Classroom对象的时候,Classroom对象同时请求数据库取出关联的Student对象,同样Student也会关联到更多的Score对象。如果我们只需要展示出所有classroom数据表中的数据,那么取出的Student对象和Score对象根本就是用不到的数据,而且这些操作代价很高。
        那么看看StudentMapper中生成一个Student对象的时候的代码:

protected function createObject(array $data) {
	$student = new Student($data['id']);
	$student->setName($data['name']);
	// setClassroom()
	$cMapper = PersistanceFactory::getFactory('demo\domain\Classroom')->getMapper();
	$classroom = $cMapper->findById($data['cid']);
	$student->setClassroom($classroom);
	// setScores
	$scoreMapper = PersistanceFactory::getFactory('demo\domain\Score')->getMapper();
	$scores = $scoreMapper->findByStudentId($data['id']);
	$student->setScores($scores);

	return $student;
}
        一个Student对象的生成就查询了相关联的Score数据(findByStudentId()方法),如果相关联的数据量很多的话,效率也就低了。
       

        SocreMapper中的findByStudentId方法代码:

public function findByStudentId($sid) {
	self::$selectByStudentIdStmt->execute(array($sid));
	$raws = self::$selectByStudentIdStmt->fetchAll(\PDO::FETCH_ASSOC);
	
	return $this->getCollection($raws);                  
}  
        可以看到findByStudentId()立即对数据库进行查询了,而不管需不需要用到这些数据。

        findByStudentId()返回的是Collection,那么我们可以对Collection进行操作,让这个Collection只有在我们访问它里面的数据时,Collection自己去执行数据库查询并保存在Colletion自己的属性中。如果我们不去访问Collection中的数据,那么Colletion里面就是空的(没有数据)。

        

        DeferredScoreCollection代码:

class DeferredScoreCollection extends ScoreCollection {
    private $stmt;
    private $valueArray;
    private $run = false;

    public function __construct(Mapper $mapper, \PDOStatement $stmtHandle, array $valueArray ) {
        parent::__construct(null, $mapper); 
        $this->stmt = $stmtHandle;
        $this->valueArray = $valueArray;
    }

    /**
     * 重写Colletion的notifyAccess()方法
     */
    protected function notifyAccess() {
        if (!$this->run) {
            $this->stmt->execute($this->valueArray);
            $this->raws = $this->stmt->fetchAll();
            $this->total = count($this->raws);
        } 
        
        $this->run = true;
    }
} 
        DeferredScoreCollection继承自Colletion。在上面的代码中可以看到, 只有客户端代码需要查询到Colletion中的代码时才去查询数据库(notifyAccess())。


        修改成延迟加载后的SocreMapper中的findByStudentId方法:

public function findByStudentId($sid) {
	return new DeferredScoreCollection($this, self::$selectByStudentIdStmt, array($sid));
}
        修改前后的findByStudentId方法返回的Colletion不一样而已,后者实现了延迟加载,只有当用到的时候才去数据库中获取数据。


         领域对象工厂

         Mapper的功能很灵活和强大,但又造成了包含了太多的功能,这也一定程度地加大了它的复杂性。那么我们可以分解Mapper,把它里面的功能拆分出来由其它类来实现。我们可以使用细粒度的模式重新提取出Mapper中的部分功能,然后再由它们构成一个完整的Mapper。

         首先我们可以提出Mapper中的createObject方法,让领域对象工厂(DomainObject Factory)来实现。

         下面为类图:

                                         

         领域对象只有一个核心功能:创建领域对象。 


         DomainObjectFactory的代码:

namespace demo\mapper;

require_once 'demo/domain/DomainObject.php';
require_once 'demo/domain/Classroom.php';
require_once 'demo/domain/Student.php';
require_once 'demo/domain/Score.php';
require_once 'demo/domain/ObjectWatcher.php';
require_once 'demo/mapper/PersistanceFactory.php';

use demo\domain\DomainObject;
use demo\domain\Classroom;
use demo\domain\Student;
use demo\domain\Score;
use demo\domain\ObjectWatcher;

/**
 * 
 * @author happen
 */
abstract class DomainObjectFactory {
	public abstract function createObject(array $data);
	    
    protected function getFromMap($class, $id) {
        return ObjectWatcher::exists($class, $id);
    }
    
    protected function addToMap(DomainObject $obj) {
        return ObjectWatcher::add($obj );
    }
}

class ClassroomObjectFactory extends DomainObjectFactory {
	public function createObject(array $data) {
        $old = $this->getFromMap('demo\domain\Classroom', $array['id']);
        if ( $old ) { 
        	return $old; 
        }
		
		$classroom = new Classroom($data['id']);
		$classroom->setName($data['name']);
		// 关联的student集合
		$stuMapper = PersistanceFactory::getFactory('demo\domain\Student')->getMapper();
		$classroom->setStudents($stuMapper->findByClassroomId($data['id']));
		// 加入到ObjectWatcher
		$this->addToMap($classroom);
        $classroom->markClean();
		return $classroom;
	}
}

class StudentObjectFactory extends DomainObjectFactory {
	public function createObject(array $data) {
	    $old = $this->getFromMap('demo\domain\Student', $array['id']);
        if ( $old ) { 
        	return $old; 
        }
		
		$student = new Student($data['id']);
		$student->setName($data['name']);
		// setClassroom()
		$cMapper = PersistanceFactory::getFactory('demo\domain\Classroom')->getMapper();
		$classroom = $cMapper->findById($data['cid']);
		$student->setClassroom($classroom);
		// setScores
		$scoreMapper = PersistanceFactory::getFactory('demo\domain\Score')->getMapper();
		$scores = $scoreMapper->findByStudentId($data['id']);
		$student->setScores($scores);
		// 加入到ObjectWatcher
		$this->addToMap($student);
        $student->markClean();
		
		return $student;
	}
}

class ScoreObjectFactory extends DomainObjectFactory {
	public function createObject(array $data) {
	    $old = $this->getFromMap('demo\domain\Score', $array['id']);
        if ( $old ) { 
        	return $old; 
        }
		
		$score = new Score($data['id']);
		$score->setScore($data['score']);
		$score->setCourseName($data['course_name']);
		// setStudent
		$stuMapper = PersistanceFactory::getFactory('demo\domain\Student')->getMapper();
		$stuObj = $stuMapper->findById($data['sid']);
		$score->setStudent($stuObj);
		// 加入到ObjectWatcher
		$this->addToMap($score);
        $score->markClean();
        
		return $score;
	}
}


        DomainObjectFactory可以代替Mapper中的createObject和子类的doCreateObject方法,其它需要生成领域对象的地方都可以使用它来进行。领域对象工厂消除了数据库原始数据与对象字段数据之间的耦合。可以在createObject方法中执行任意调整,这个过程对于调用者来说是透明的,只要提供可用的数据即可。而且它能够更加容易地用在测试中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值