[php]標記投射和工作單元

來源:互聯網
上載者:User
[php]標記映射和工作單元
標記映射

系統中可能存在兩個值相同,但又不是同一個引用的對象,這樣的重複對象可能是從資料庫中讀出來的,這樣就造成了不必要的查詢。

標記映射是一個類ObjectWatcher,它負責管理進程中的領域對象,以保證進程中不出現重複對象。

標記映射可以防止重新讀取資料庫查詢資料,只有當ObjectWatcher類中不存在標記映射對應的對象時才去查詢資料庫。這樣就保證了在一個進程中,一條資料只對應一個對象。

代碼很容易懂,都是一些存取數組值的操作。

ObjectWatcher代碼:

namespace demo\domain;use \demo\domain\DomainObject;/** *  標記映射 */class ObjectWatcher {private static $instance;// 標記映射private $all = array();private function __construct() {}public  static function getInstance() {if (!isset(self::$instance)) {self::$instance = new self();}return self::$instance;}/** * 獲得對象對應的鍵值 * @param DomainObject $obj */public function getGobalKey(DomainObject $obj) {$key = get_class($obj) . '_' . $obj->getId();return $key;}/** * 添加到all * @param DomainObject $obj */public static function add(DomainObject $obj) {$instance = self::getInstance();$key = $instance->getGobalKey($obj);$instance->all[$key] = $obj;}/** * 從all中刪除 * @param DomainObject $obj */public static function delete(DomainObject $obj) {$instance = self::getInstance();$key = $instance->getGobalKey($obj);unset($instance->all[$key]);}/** * 判斷標記是否存在 * @param string $className * @param int $id */public  static function exists($className, $id) {$instance = self::getInstance();$key = "{$className}_{$id}";if (isset($instance->all[$key])) {return $instance->all[$key];}return null;}}
那麼在哪裡做標記呢?當然是產生查詢對象的地方,分別有Mapper::find()、Mapper::insert()、Mapper::createObject()。 Mapper中新增加了addToMap()和getFromMap()。(其它方法沒有改變,所以看以忽略吧。)

Mapper代碼:

namespace demo\mapper;use \demo\base\AppException;use \demo\base\ApplicationRegistry;use \demo\domain\DomainObject;use \demo\domain\ObjectWatcher;/** * Mapper */abstract  class Mapper {// PDOprotected static $PDO;// configprotected static  $dsn, $dbUserName, $dbPassword;// PDO選項protected static $options = array(    \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',    \PDO::ATTR_ERRMODE,    \PDO::ERRMODE_EXCEPTION,); public function __construct() {if (!isset(self::$PDO)) {// ApplicationRegistry擷取資料庫連接資訊$appRegistry = ApplicationRegistry::getInstance();self::$dsn = $appRegistry->getDsn();self::$dbUserName = $appRegistry->getDbUserName();self::$dbPassword = $appRegistry->getDbPassword();if (!self::$dsn || !self::$dbUserName || !self::$dbPassword) {throw  new AppException('Mapper init failed!');}self::$PDO = new \PDO(self::$dsn, self::$dbUserName, self::$dbPassword, self::$options);}}/** * 尋找指定ID * @param int $id */public function findById($id) {// 從ObjectWatcher中擷取$obj = $this->getFromMap($id);if (!is_null($obj)) {return $obj;}$pStmt = $this->getSelectStmt();$pStmt->execute(array($id));$data = $pStmt->fetch();$pStmt->closeCursor();if (!is_array($data) || !isset($data['id'])) {return $obj;}$obj = $this->createObject($data);return $obj;}/** * 返回Collection */public function findAll() {$pStmt = $this->getSelectAllStmt();$pStmt->execute(array());$raws = $pStmt->fetchAll(\PDO::FETCH_ASSOC);$collection = $this->getCollection($raws);return $collection;}/** * 插入資料 * @param \demo\domain\DomainObject $obj */public function insert(DomainObject $obj) {$flag = $this->doInsert($obj);// 儲存或者更新ObjectWatcher的$all[$key]的對象$this->addToMap($obj);return $flag;}/** *  更新對象 * @param \demo\domain\DomainObject $obj */public  function update(\demo\domain\DomainObject $obj) {$flag = $this->doUpdate($obj);return $flag;}/** * 刪除指定ID * @param int $id */public function deleteById($id) {$pStmt = $this->getDeleteStmt();$flag = $pStmt->execute(array($id));return $flag;}/** * 產生一個$data中值屬性的對象 * @param array $data */public function createObject(array $data) {// 從ObjectWatcher中擷取$obj = $this->getFromMap($data['id']);if (!is_null($obj)) {return $obj;}// 建立對象$obj = $this->doCreateObject($data);// 添加到ObjectWatcher$this->addToMap($obj);return $obj;}/** * 返回對應key標記的對象 * @param int $id */private function getFromMap($id) {return ObjectWatcher::exists($this->getTargetClass(), $id);}/** * 添加對象到標記映射ObjectWatcher類 * @param DomainObject $obj */private function addToMap(DomainObject $obj) {return ObjectWatcher::add($obj);}/** * 返回子類Collection * @param array $raw */public function getCollection(array $raws) {return $this->getFactory()->getCollection($raws);}/** * 返回子類持久化工廠對象 */public function getFactory() {return PersistanceFactory::getFactory($this->getTargetClass());}protected abstract function doInsert(\demo\domain\DomainObject $obj);protected abstract function doCreateObject(array $data);protected abstract function getSelectStmt();protected abstract function getSelectAllStmt();protected abstract function doUpdate(\demo\domain\DomainObject $obj);protected abstract function getDeleteStmt();protected abstract function getTargetClass();}
大部分代碼是之前的,修改的只是一小部分。下面圖一張:


現在,當Mapper從資料庫中取出的資料對應成的對象都被標記到ObjectWatcher了,而且不需要對對象手動操作標記到ObjectWatcher,Mapper就已經幫你完成了。這樣帶來的好處是可以減少對資料庫的操作和新對象的建立,比如find、createObject。但這也許可能帶來問題,如果你的程式需要並發處理資料,那麼被標記的對象資料就可能不一致了,你在這個時候可能需要對資料加鎖。


工作單元

有些時候,我們可能沒有改變資料的任何值卻向資料庫多次儲存該資料,這當然是不必要的吧。工作單元可以使你只儲存那些需要的對象。工作單元可以在一次請求即將結束時,把在這次請求中發生變化的對象儲存到資料庫中。一次請求的最後是在控制器(Controller)調用完Command和View之後,那麼我們就可以在這裡讓工作單元執行任務。

標記映射的作用是在處理過程開始時向資料庫載入不必要的對象,而工作單元則是在處理過程之後防止不必要的對象儲存到資料庫中。這兩個工作方式就像是互補的。

為了判斷哪些資料庫的操作是必要的,那就需要跟蹤與對象相關的各種事件(比如:setter()重新設定了對象的屬性值)。跟蹤工作當然最好放在被跟蹤的對象中。

修改過的ObjectWatcher類:

namespace demo\domain;use \demo\domain\DomainObject;/** *  標記映射 */class ObjectWatcher {private static $instance;// 標記映射private $all = array();// 儲存建立對象private $new = array();// 儲存被修改過的對象(“髒對象”)private $dirty = array();// 儲存刪除對象private $delete = array();private function __construct() {}public  static function getInstance() {if (!isset(self::$instance)) {self::$instance = new self();}return self::$instance;}/** * 獲得對象對應的鍵值 * @param DomainObject $obj */public function getGobalKey(DomainObject $obj) {$key = get_class($obj) . '_' . $obj->getId();return $key;}/** * 添加到all * @param DomainObject $obj */public static function add(DomainObject $obj) {$instance = self::getInstance();$key = $instance->getGobalKey($obj);$instance->all[$key] = $obj;}/** * 從all中刪除 * @param DomainObject $obj */public static function delete(DomainObject $obj) {$instance = self::getInstance();$key = $instance->getGobalKey($obj);unset($instance->all[$key]);}/** * 添加到new * @param DomianObject $obj */public static function addNew(DomainObject $obj) {$instance = self::getInstance();$instance->new[] = $obj;}/** * 添加到dirty * @param DomianObject $obj */public static function addDirty(DomainObject $obj) {$instance = self::getInstance();if (!in_array($obj, $instance->dirty, true)) {$instance->dirty[$instance->getGobalKey($obj)] = $obj;}}/** * 添加到delete * @param DomainObject $obj */public static function addDelete(DomainObject $obj) {$instance = self::getInstance();$instance->delete[$instance->getGobalKey($obj)] = $obj;}/** * 清除標記dirty new delete * @param DomainObject $obj */public static function addClean(DomainObject $obj) {$instance = self::getInstance();// unset刪除儲存的對象unset($instance->dirty[$instance->getGobalKey($obj)]);unset($instance->delete[$instance->getGobalKey($obj)]);// 刪除new中的對象$instance->new = array_filter($instance->new, function($a) use ($obj) {return !($a === $obj);});}/** * 判斷標記是否存在 * @param string $className * @param int $id */public  static function exists($className, $id) {$instance = self::getInstance();$key = "{$className}_{$id}";if (isset($instance->all[$key])) {return $instance->all[$key];}return null;}/** * 對new dirty delete 中的標記對象執行操作 */public function performOperations() {$instance = self::getInstance();// newforeach ($instance->new as $obj) {$obj->finder()->insert($obj);}// dirtyforeach ($instance->dirty as $obj) {$obj->finder()->update($obj);}// deleteforeach ($instance->delete as $obj) {$obj->finder()->delete($obj);}$this->new = array();$this->dirty = array();$this->delete = array();}}
ObjectWatcher依然是標記映射,只是在這裡增加了跟蹤系統中對象的變化的功能。ObjectWatcher類提供了尋找、刪除、添加對象到資料庫的機制。

由於ObjectWatcher的操作上對對象的操作,所以由這些對象自己來來執行ObjectWatcher是很適合的。

修改過的DomainObject類(markNew()、markDirty()、markDelete()、markClean()):

namespace demo\domain;use \demo\domain\HelperFactory;use \demo\domain\ObjectWatcher;/** * 領域模型抽象基類 */abstract class DomainObject {protected  $id = -1;public function __construct($id = null) {if (is_null($id)) {// 標記為new 建立$this->markNew();} else {$this->id = $id;}}public function getId() {return $this->id;}public function setId($id) {$this->id = $id;$this->markDirty();}public function markNew() {ObjectWatcher::addNew($this);}public function markDirty() {ObjectWatcher::addDirty($this);}public function markDeleted() {ObjectWatcher::addDelete($this);}public function markClean() {ObjectWatcher::addClean($this);}public static function getCollection($type) {return HelperFactory::getCollection($type);}public function collection() {return self::getCollection(get_class($this));}public static function getFinder($type) {return HelperFactory::getFinder($type);}public function finder() {return self::getFinder(get_class($this));}}
DomainObject和ObjectWatcher的關係圖一張:

修改過的Mapper(和上面相同部分略去了,太佔位子了):

/** * Mapper */abstract  class Mapper {//.../** * 尋找指定ID * @param int $id */public function findById($id) {// 從ObjectWatcher中擷取$obj = $this->getFromMap($id);if (!is_null($obj)) {return $obj;}$pStmt = $this->getSelectStmt();$pStmt->execute(array($id));$data = $pStmt->fetch();$pStmt->closeCursor();if (!is_array($data) || !isset($data['id'])) {return $obj;}$obj = $this->createObject($data);return $obj;}/** * 插入資料 * @param \demo\domain\DomainObject $obj */public function insert(DomainObject $obj) {$flag = $this->doInsert($obj);// 儲存或者更新ObjectWatcher的$all[$key]的對象$this->addToMap($obj);$obj->markClean();// 調試用的echo 'insert :' . get_class($obj) . '_' . $obj->getName() . '_' . $obj->getId() .'
';return $flag;}/** * 更新對象 * @param \demo\domain\DomainObject $obj */public function update(\demo\domain\DomainObject $obj) {$flag = $this->doUpdate($obj);$obj->markClean();// 調試用的echo 'update :' . get_class($obj) . '_' . $obj->getName() . '_' . $obj->getId() .'
';return $flag;}/** * 產生一個$data中值屬性的對象 * @param array $data */public function createObject(array $data) {// 從ObjectWatcher中擷取$obj = $this->getFromMap($data['id']);if (!is_null($obj)) {return $obj;}// 建立對象$obj = $this->doCreateObject($data);// 添加到ObjectWatcher$this->addToMap($obj);// 清除new標記 ObjectWatcher::addClean($obj);return $obj;}//...}
可以看到Mapper中修改的部分都是有改變對象的事件發生,即find()、update()、insert()、delete()。

對象的變化都能被跟蹤到了,那麼應該在哪裡處理這些變化過的對象(“髒資料”)呢?上面說到了,應該在一次請求即將完成的時候。

一次請求即將結束時,Controller中調用工作單元(同樣省略了沒改變的代碼):

namespace demo\controller;/** * Controller */class Controller {// ...private function handleReuqest() {$request = new \demo\controller\Request();$appController = \demo\base\ApplicationRegistry::getInstance()->getAppController();// 執行完所有Command,有可能存在forwardwhile ($cmd = $appController->getCommand($request)) {// var_dump($cmd);$cmd->execute($request);// 把當前Command設為已執行過$request->setLastCommand($cmd);}// 工作單元執行任務ObjectWatcher::getInstance()->performOperations();// 擷取視圖$view = $appController->getView($request);// 顯示視圖$this->invokeView($view);}// ...}
ObjectWatcher::getInstance()->performOperations()


好的,現在來個使用例子吧:

namespace demo\command;use demo\domain\Classroom;use demo\base\ApplicationRegistry;use demo\domain\ObjectWatcher;use demo\domain\HelperFactory;class Test extends Command {protected function doExecute(\demo\controller\Request $request) {$crMapper = HelperFactory::getFinder('demo\domain\Classroom');// 新建立的對象 markNew()$crA = new Classroom();$crA->setName('四年(3)班');// 修改後的“髒”資料$crB = $crMapper->findById(1);$crB->setName("五年(2)班");}}
輸入的Url:
localhost/demo/runner.php?cmd=Test
輸出結果與預期的一樣:
insert :demo\domain\Classroom_四年(3)班_58update :demo\domain\Classroom_五年(2)班_1

現在對領域對象的管理有了較大的改進了。還有,我們使用模式的目的是提高效率,而不是降低效率。


  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.