[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方法中執行任意調整,這個過程對於調用者來說是透明的,只要提供可用的數據即可。而且它能夠更加容易地用在測試中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章