Upload
jace-ju
View
23.395
Download
2
Embed Size (px)
DESCRIPTION
介紹 Plugin 機制
Citation preview
購物車程式架構簡介打造易擴充的系統架構Jace Ju
從程式面來看購物車
購物車
從程式面來看購物車
包含了計算邏輯
購物車
從程式面來看購物車
加入商品
收集瀏覽器回傳的資料
購物車
從程式面來看購物車
加入商品
SessionDatabase
將計算結果存到Session
購物車
從程式面來看購物車
加入商品
SessionDatabase
如果要長期保留就改用 Database
購物車
從程式面來看購物車
加入商品 更新數量
SessionDatabase
更新數量可以透過 AJAX 或是直接POST 後重新載入,不要在前端直接用JavaScript 計算
購物車
從程式面來看購物車
加入商品 更新數量 移除商品
SessionDatabase 移除商品與更新數量
是相似的動作
購物車
從程式面來看購物車
加入商品 更新數量 移除商品
SessionDatabase
結帳
最後進入金流並完成訂單
購物車
從程式面來看購物車
加入商品 更新數量 移除商品
SessionDatabase
結帳
這些只是購物車的基本功能而已...
購物車
從程式面來看購物車
加入商品 更新數量 移除商品
SessionDatabase
結帳
最可怕的是...
購物車
從程式面來看購物車
加入商品 更新數量 移除商品
SessionDatabase
結帳
促銷活動的種類
促銷活動的種類
• 折價券 應該沒有比這個更常見的了
促銷活動的種類
• 折價券
• 紅利兌換 數學要很好...
促銷活動的種類
• 折價券
• 紅利兌換
• 滿 XXX 免運費還算簡單
促銷活動的種類
• 折價券
• 紅利兌換
• 滿 XXX 免運費
• 買 A 送 B 有點麻煩...如果要拿掉 A 時,也要同時拿掉 B
促銷活動的種類
• 折價券
• 紅利兌換
• 滿 XXX 免運費
• 買 A 送 B
• 紅綠標商品介面很難做
促銷活動的種類
• 折價券
• 紅利兌換
• 滿 XXX 免運費
• 買 A 送 B
• 紅綠標商品
• 限量搶購 庫存很難搞
促銷活動的種類
• 折價券
• 紅利兌換
• 滿 XXX 免運費
• 買 A 送 B
• 紅綠標商品
• 限量搶購
• ...更多折磨工程師的行銷手段!!
每個人心裡的 OS
每個人心裡的 OS
客戶
請交給我們一個超多功能的系統!
每個人心裡的 OS
客戶
請交給我們一個超多功能的系統!
消費者
請給我們一個安心購物的平台!
每個人心裡的 OS
客戶
請交給我們一個超多功能的系統!
消費者
請給我們一個安心購物的平台!
請賜死...不對
工程師
每個人心裡的 OS
客戶
請交給我們一個超多功能的系統!
消費者
請給我們一個安心購物的平台!
請賜給我們...一個容易擴充的購物車架構!
工程師
常見的購物車架構
常見的購物車架構
購物車
常見的購物車架構
購物車
Session
儲存購物車中的商品資訊
常見的購物車架構
購物車
Session
Database
在資料庫中尋找商品
常見的購物車架構
購物車
Session
Database
功能 1
功能 2
功能 3
其他的購物車功能
如何讓購物車容易測試與擴充?
如何讓購物車容易測試與擴充?
• 主架構只保留最基本的計算邏輯
簡化架構以求測試容易
如何讓購物車容易測試與擴充?
• 主架構只保留最基本的計算邏輯▫ 抽離 Session 與 Database
如何讓購物車容易測試與擴充?
• 主架構只保留最基本的計算邏輯▫ 抽離 Session 與 Database
▫ 加入可擴充的機制,讓經常變動的功能變成 Plugin
如何讓購物車容易測試與擴充?
• 主架構只保留最基本的計算邏輯▫ 抽離 Session 與 Database
▫ 加入可擴充的機制,讓經常變動的功能變成 Plugin
• 測試時儘量避免測試要依賴系統
如何讓購物車容易測試與擴充?
• 主架構只保留最基本的計算邏輯▫ 抽離 Session 與 Database
▫ 加入可擴充的機制,讓經常變動的功能變成 Plugin
• 測試時▫ Session 改用 Storage
如何讓購物車容易測試與擴充?
• 主架構只保留最基本的計算邏輯▫ 抽離 Session 與 Database
▫ 加入可擴充的機制,讓經常變動的功能變成 Plugin
• 測試時▫ Session 改用 Storage
▫ Database 改用 Dao
如何讓購物車容易測試與擴充?
• 主架構只保留最基本的計算邏輯▫ 抽離 Session 與 Database
▫ 加入可擴充的機制,讓經常變動的功能變成 Plugin
• 測試時▫ Session 改用 Storage
▫ Database 改用 Dao
▫ 主架構與 Plugin 分開測試
Storage
Storage
• 用最單純的 Array 來做為介質 避免自動測試工具不支援 Session
Storage
• 用最單純的 Array 來做為介質
• 提供操作介質的介面 將操作抽象化就可以封裝介質的不同
Storage
• 用最單純的 Array 來做為介質
• 提供操作介質的介面
• 真正寫入 Session 的部份交給子類別
在實際運作時,再改用子類別
// Storage 類別長什麼樣子?
// Storage 類別長什麼樣子?
class Cart_Storage {
}
// Storage 類別長什麼樣子?
class Cart_Storage implements ArrayAccess, Countable {
}
為了能像操作 array 一般地操作 Storage ,所以實作 ArrayAccess 及 Countable 兩種介面
// Storage 類別長什麼樣子?
class Cart_Storage implements ArrayAccess, Countable {
protected $_contanier = null;
}
儲存介質
// Storage 類別長什麼樣子?
class Cart_Storage implements ArrayAccess, Countable {
protected $_contanier = null;
public function __construct()
{
$this->_container = array();
}
}
介質初始化,在測試時不需載入資料
// Storage 類別長什麼樣子?
class Cart_Storage implements ArrayAccess, Countable {
protected $_contanier = null;
public function __construct()
{
$this->_container = array();
}
public function clear()
{
$this->_container = array();
}
}
清除介質
// Storage 類別長什麼樣子?
class Cart_Storage implements ArrayAccess, Countable {
protected $_contanier = null;
public function __construct()
{
$this->_container = array();
}
public function clear()
{
$this->_container = array();
}
public function offsetSet($offset, $value) {}
public function offsetExists($offset) {}
public function offsetUnset($offset) {}
public function offsetGet($offset) {}
}
ArrayAccess 介面提供的方法,在這裡加入操作介質的程式
// Storage 類別長什麼樣子?
class Cart_Storage implements ArrayAccess, Countable {
protected $_contanier = null;
public function __construct()
{
$this->_container = array();
}
public function clear()
{
$this->_container = array();
}
public function offsetSet($offset, $value) {}
public function offsetExists($offset) {}
public function offsetUnset($offset) {}
public function offsetGet($offset) {}
public function count()
{
return count($this->_container);
}
}Countable 介面提供的方法,
這裡回傳介質的大小
// Storage 的 Session 子類別長什麼樣子?
// Storage 的 Session 子類別長什麼樣子?
class Cart_Storage_Session {
}
// Storage 的 Session 子類別長什麼樣子?
class Cart_Storage_Session extends Cart_Storage {
}
繼承 Cart_Storage 以沿用處理介質的方法
// Storage 的 Session 子類別長什麼樣子?
class Cart_Storage_Session extends Cart_Storage {
public function __construct()
{
if (!isset($_SESSION)) { session_start(); }
if (isset($_SESSION['CART'])) {
$this->_container = $_SESSION['CART'];
} else {
$this->_container = array();
}
}
}
物件建構時,從 Session 取回資料置入介質
// Storage 的 Session 子類別長什麼樣子?
class Cart_Storage_Session extends Cart_Storage {
public function __construct()
{
if (!isset($_SESSION)) { session_start(); }
if (isset($_SESSION['CART'])) {
$this->_container = $_SESSION['CART'];
} else {
$this->_container = array();
}
}
public function __destruct()
{
$_SESSION['CART'] = $this->_container;
}
}
在物件消滅前,把介質存回 Session
// Storage 的 Session 子類別長什麼樣子?
class Cart_Storage_Session extends Cart_Storage {
public function __construct()
{
if (!isset($_SESSION)) { session_start(); }
if (isset($_SESSION['CART'])) {
$this->_container = $_SESSION['CART'];
} else {
$this->_container = array();
}
}
public function __destruct()
{
$_SESSION['CART'] = $this->_container;
}
public function clear()
{
$_SESSION['CART'] = $this->_container = array();
}
}覆蓋清除的方法
// 以加入購物車為例:
// 以加入購物車為例:
class Cart {
}
// 以加入購物車為例:
class Cart {
protected $_storage = null;
public function setStorage(Cart_Storage $storage)
{
$this->_storage = $storage;
}
}
利用 setStorage 方法讓我們可以從外部注入 Storage 物件
// 以加入購物車為例:
class Cart {
protected $_storage = null;
public function setStorage(Cart_Storage $storage)
{
$this->_storage = $storage;
}
public function addItem($itemKey)
{
$this->_storage[$itemKey] = array(
'quantity' => 1,
);
}
}
購物車商品資訊改用 Storage 物件存放
// 以加入購物車為例:
class Cart {
protected $_storage = null;
public function setStorage(Cart_Storage $storage)
{
$this->_storage = $storage;
}
public function addItem($itemKey)
{
$this->_storage[$itemKey] = array(
'quantity' => 1,
);
}
}
$cart = new Cart();
建立一個新購物車來測試
// 以加入購物車為例:
class Cart {
protected $_storage = null;
public function setStorage(Cart_Storage $storage)
{
$this->_storage = $storage;
}
public function addItem($itemKey)
{
$this->_storage[$itemKey] = array(
'quantity' => 1,
);
}
}
$cart = new Cart();
$cart->setStorage(new Cart_Storage());
用外部注入方式來使用 Storage 物件
// 以加入購物車為例:
class Cart {
protected $_storage = null;
public function setStorage(Cart_Storage $storage)
{
$this->_storage = $storage;
}
public function addItem($itemKey)
{
$this->_storage[$itemKey] = array(
'quantity' => 1,
);
}
}
$cart = new Cart();
$cart->setStorage(new Cart_Storage_Session());
也可以改用 Session Storage
Dao
Dao
• 目的是把 Database 與主架構做分離
這樣就不再依賴資料庫
Dao
• 目的是把 Database 與主架構做分離
• 僅提供 API ,不關心資料從何處取得
我們只關心回傳給購物車的格式是否正確
Dao
• 目的是把 Database 與主架構做分離
• 僅提供 API ,不關心資料從何處取得
• 真正存取 Database 的部份交給子類別
在實際運作時,再改用子類別
// Dao 類別長什麼樣子?
// Dao 類別長什麼樣子?
class Cart_Dao {
}
// Dao 類別長什麼樣子?
class Cart_Dao {
public function find($key)
{
return array();
}
}
用來取得一筆資料用的方法
// Dao 類別長什麼樣子?
class Cart_Dao {
public function find($key)
{
return array();
}
public function findAll($keyList)
{
return array();
}
} 用來取得多筆資料用的方法
// Dao 的 Db 子類別長什麼樣子?
// Dao 的 Db 子類別長什麼樣子?
class Cart_Dao_Product {
}
// Dao 的 Db 子類別長什麼樣子?
class Cart_Dao_Product extends Cart_Dao {
}
繼承 Cart_Dao 類別
// Dao 的 Db 子類別長什麼樣子?
class Cart_Dao_Product extends Cart_Dao {
public function find($key)
{
}
}
覆寫父類別的 find 方法
// Dao 的 Db 子類別長什麼樣子?
class Cart_Dao_Product extends Cart_Dao {
public function find($key)
{
$productTable = new Products();
$productData = array();
if ($productRow =
$productTable->fetchRow(array('sn = ?' => $key))) {
$productData = $productRow->toArray();
}
return $productData;
}
}
找到 $key 對應的商品後就回傳
// Dao 的 Db 子類別長什麼樣子?
class Cart_Dao_Product extends Cart_Dao {
public function find($key)
{
$productTable = new Products();
$productData = array();
if ($productRow =
$productTable->fetchRow(array('sn = ?' => $key))) {
$productData = $productRow->toArray();
}
return $productData;
}
public function findAll($keyList)
{
}
}
覆寫父類別的 findAll 方法
// Dao 的 Db 子類別長什麼樣子?
class Cart_Dao_Product extends Cart_Dao {
public function find($key)
{
$productTable = new Products();
$productData = array();
if ($productRow =
$productTable->fetchRow(array('sn = ?' => $key))) {
$productData = $productRow->toArray();
}
return $productData;
}
public function findAll($keyList)
{
$productTable = new Products();
$productRowset = $productTable->fetchRowsetBySn($keyList);
}
}
透過 Zend_Db_Table 來取得 Rowset
// Dao 的 Db 子類別長什麼樣子?
class Cart_Dao_Product extends Cart_Dao {
public function find($key)
{
$productTable = new Products();
$productData = array();
if ($productRow =
$productTable->fetchRow(array('sn = ?' => $key))) {
$productData = $productRow->toArray();
}
return $productData;
}
public function findAll($keyList)
{
$productTable = new Products();
$productRowset = $productTable->fetchRowsetBySn($keyList);
$resultDataList = array();
foreach ($productRowset as $productRow) {
$resultDataList[$productRow->sn] = $productRow->toArray();
}
return $resultDataList;
}
}
將取得的 Rowset 轉換為 Array
// 以加入購物車為例:
// 以加入購物車為例:
class Cart {
}
// 以加入購物車為例:
class Cart {
protected $_dao = null;
public function setDao(Cart_Dao $dao)
{
$this->_dao = $dao;
}
}
利用 setDao 方法讓我們可以從外部注入 Dao 物件
// 以加入購物車為例:
class Cart {
protected $_dao = null;
public function setDao(Cart_Dao $dao)
{
$this->_dao = $dao;
}
public function addItem($itemKey)
{
if ($productData = $this->_dao->find($itemKey)) {
$this->_storage[$itemKey] = array(
'quantity' => 1,
);
}
}
}
有抓到商品資料再存到 Storage 裡沒有的話就做錯誤處理 (這邊省略)
// 以加入購物車為例:
class Cart {
protected $_dao = null;
public function setDao(Cart_Dao $dao)
{
$this->_dao = $dao;
}
public function addItem($itemKey)
{
if ($productData = $this->_dao->find($itemKey)) {
$this->_storage[$itemKey] = array(
'quantity' => 1,
);
}
}
}
$cart = new Cart();
建立一個測試用的購物車物件
// 以加入購物車為例:
class Cart {
protected $_dao = null;
public function setDao(Cart_Dao $dao)
{
$this->_dao = $dao;
}
public function addItem($itemKey)
{
if ($productData = $this->_dao->find($itemKey)) {
$this->_storage[$itemKey] = array(
'quantity' => 1,
);
}
}
}
$cart = new Cart();
$cart->setDao(new Cart_Dao());
用外部注入的方式使用 Dao 物件
// 以加入購物車為例:
class Cart {
protected $_dao = null;
public function setDao(Cart_Dao $dao)
{
$this->_dao = $dao;
}
public function addItem($itemKey)
{
if ($productData = $this->_dao->find($itemKey)) {
$this->_storage[$itemKey] = array(
'quantity' => 1,
);
}
}
}
$cart = new Cart();
$cart->setDao(new Cart_Dao_Product());
改從資料庫抓取商品資料
Plugin 模式
Plugin 模式
• 在主程式裡呼叫 Plugin 流程還是由主程式控制Plugin 只負責提供額外功能
Plugin 模式
• 在主程式裡呼叫 Plugin
• 有點像 Template Method 和 Observer 的混血
複合模式
Plugin 模式
• 在主程式裡呼叫 Plugin
• 有點像 Template Method 和 Observer 的混血
定義好主流程,其他的實作透過子類別 Hook 方法來完成
Don't call me, I call you.
Plugin 模式
• 在主程式裡呼叫 Plugin
• 有點像 Template Method 和 Observer 的混血
一旦有動作,會立即通知訂閱者做改變
Plugin 模式
• 在主程式裡呼叫 Plugin
• 有點像 Template Method 和 Observer 的混血
• 最好在設計階段就考慮進去
跟 AOP 很像,但插入機制是寫死在程式裡;而 AOP 則是可以動態加入
Plugin 模式
• 在主程式裡呼叫 Plugin
• 有點像 Template Method 和 Observer 的混血
• 最好在設計階段就考慮進去
• 與 PoEAA 定義的 Plugin Pattern 是不一樣的
Patterns of Enterprise Application Architecture
by Martin Fowler
Plugin 模式
• 在主程式裡呼叫 Plugin
• 有點像 Template Method 和 Observer 的混血
• 最好在設計階段就考慮進去
• 與 PoEAA 定義的 Plugin Pattern 是不一樣的
在不同的運行環境,執行不一樣的動作
Plugin 模式圖解
Plugin 模式圖解
購物車
Plugin 模式圖解
用戶點選頁面連結加入商品
購物車
Plugin 模式圖解
用戶點選頁面連結加入商品
尋找商品資料並驗證是否可以加入
購物車
Plugin 模式圖解
用戶點選頁面連結加入商品
尋找商品資料並驗證是否可以加入
寫入 Session或 Database
購物車
Plugin 模式圖解
購物車
Plugin 模式圖解
購物車
Plugin 1
Plugin 2
Plugin 3
加入 Plugin 機制
Plugin 模式圖解
用戶點選頁面連結加入商品
購物車
Plugin 1
Plugin 2
Plugin 3
Plugin 模式圖解
用戶點選頁面連結加入商品
加入購物車前
加入購物車前
加入購物車前
購物車
Plugin 1
Plugin 2
Plugin 3
這時購物車會呼叫Plugin
Plugin 模式圖解
用戶點選頁面連結加入商品
尋找商品資料並驗證是否可以加入
加入購物車前
加入購物車前
加入購物車前
購物車
Plugin 1
Plugin 2
Plugin 3
Plugin 模式圖解
用戶點選頁面連結加入商品
尋找商品資料並驗證是否可以加入
加入購物車前 加入購物車後
加入購物車前 加入購物車後
加入購物車前 加入購物車後
購物車
Plugin 1
Plugin 2
Plugin 3
購物車再次呼叫Plugin
Plugin 模式圖解
用戶點選頁面連結加入商品
尋找商品資料並驗證是否可以加入
寫入 Session或 Database
加入購物車前 加入購物車後
加入購物車前 加入購物車後
加入購物車前 加入購物車後
購物車
Plugin 1
Plugin 2
Plugin 3
繼續完成動作
// 以加入購物車為例:
// 以加入購物車為例:
class Cart {
}
購物車類別
// 以加入購物車為例:
class Cart {
public function addItem($itemKey) {
// 真正加入購物車(...略...)
}
}
加入購物車方法
// 以加入購物車為例:
class Cart {
protected $_pluginList = array();
public function addItem($itemKey) {
// 真正加入購物車(...略...)
}
}
用陣列來記住已載入的 Plugin
// 以加入購物車為例:
class Cart {
protected $_pluginList = array();
public function addItem($itemKey) {
// 加入購物車之前foreach ($this->_pluginList => $plugin) {
$plugin->beforeAddItem($itemKey);
}
// 真正加入購物車(...略...)
}
}
在這裡插入加入購物車前的程式
// 以加入購物車為例:
class Cart {
protected $_pluginList = array();
public function addItem($itemKey) {
// 加入購物車之前foreach ($this->_pluginList => $plugin) {
$plugin->beforeAddItem($itemKey);
}
// 真正加入購物車(...略...)
// 加入購物車之後foreach ($this->_pluginList => $plugin) {
$plugin->afterAddItem($itemKey);
}
}
}
在這裡插入加入購物車後的程式
// Plugin 類別長什麼樣子??
// Plugin 類別長什麼樣子??
abstract class Cart_Plugin {
}
首先要有一個抽象類別
// Plugin 類別長什麼樣子??
abstract class Cart_Plugin {
protected $_cart = null;
}
為了可直接取得購物車的資訊所以有一個購物車屬性
// Plugin 類別長什麼樣子??
abstract class Cart_Plugin {
protected $_cart = null;
public function __construct(Cart $cart) {
$this->_cart = $cart;
}
}
購物車屬性可由建構式的參數帶入
// Plugin 類別長什麼樣子??
abstract class Cart_Plugin {
protected $_cart = null;
public function __construct(Cart $cart) {
$this->_cart = $cart;
}
public function beforeAddItem($itemKey) {}
public function afterAddItem($itemKey) {}
public function beforeUpdateItem($itemKey) {}
public function afterUpdateItem($itemKey) {}
// ... (其他方法)
}最後定義各項 Hook 方法
(預設為空實作)
// 如何加入 Plugin ??
// 如何加入 Plugin ??
class Cart {
protected $_pluginList = array();
}
剛剛的購物車類別
// 如何加入 Plugin ??
class Cart {
protected $_pluginList = array();
public function registerPlugin($name)
{
}
}
加入註冊 Plugin 的方法
// 如何加入 Plugin ??
class Cart {
protected $_pluginList = array();
public function registerPlugin($name)
{
$name = ucfirst($name);
$pluginName = 'Cart_Plugin_' . $name;
}
}
轉換成實際 Plugin 的類別名稱
// 如何加入 Plugin ??
class Cart {
protected $_pluginList = array();
public function registerPlugin($name)
{
$name = ucfirst($name);
$pluginName = 'Cart_Plugin_' . $name;
if (class_exists($pluginName, true)) {
$this->_pluginList[$name] = new $pluginName($this);
}
}
}
判斷 Plugin 類別是否存在?有的話就建立實體,並利用建構式把購物車物件傳給 Plugin 物件使用
// 如何加入 Plugin ??
class Cart {
protected $_pluginList = array();
public function registerPlugin($name)
{
$name = ucfirst($name);
$pluginName = 'Cart_Plugin_' . $name;
if (class_exists($pluginName, true)) {
$this->_pluginList[$name] = new $pluginName($this);
} else {
throw new Exception("Plugin: $pluginName does not exist!");
}
}
} Plugin 類別不存在的話,就丟出異常
// 如何加入 Plugin ??
class Cart {
protected $_pluginList = array();
public function registerPlugin($name)
{
$name = ucfirst($name);
$pluginName = 'Cart_Plugin_' . $name;
if (class_exists($pluginName, true)) {
$this->_pluginList[$name] = new $pluginName($this);
} else {
throw new Exception("Plugin: $pluginName does not exist!");
}
}
}
$cart = new Cart();
建立一個新購物車來測試
// 如何加入 Plugin ??
class Cart {
protected $_pluginList = array();
public function registerPlugin($name)
{
$name = ucfirst($name);
$pluginName = 'Cart_Plugin_' . $name;
if (class_exists($pluginName, true)) {
$this->_pluginList[$name] = new $pluginName($this);
} else {
throw new Exception("Plugin: $pluginName does not exist!");
}
}
}
$cart = new Cart();
$cart->registerPlugin('plugin1');
$cart->registerPlugin('plugin2');
加入 Plugin
// 計算小計與總計金額的 Plugin 類別範例
// 計算小計與總計金額的 Plugin 類別範例
class Cart_Plugin_Total
{
}
// 計算小計與總計金額的 Plugin 類別範例
class Cart_Plugin_Total extends Cart_Plugin
{
}
繼承抽象的 Cart_Plugin 類別
// 計算小計與總計金額的 Plugin 類別範例
class Cart_Plugin_Total extends Cart_Plugin
{
protected $_total = 0;
}
用來記住總金額的屬性
// 計算小計與總計金額的 Plugin 類別範例
class Cart_Plugin_Total extends Cart_Plugin
{
protected $_total = 0;
public function beforeRefreshCart(&$iterator)
{
$this->_total = 0;
}
}
更新購物車前先將總金額歸零
// 計算小計與總計金額的 Plugin 類別範例
class Cart_Plugin_Total extends Cart_Plugin
{
protected $_total = 0;
public function beforeRefreshCart(&$iterator)
{
$this->_total = 0;
}
public function afterRefreshItem($itemKey, &$iterator)
{
$itemData = $iterator[$itemKey];
$iterator[$itemKey]['subtotal'] = $itemData['quantity'] *
$itemData['price'];
$this->_total += $iterator[$itemKey]['subtotal'];
}
}
在更新商品項目後,更新商品小計與總金額
// 計算小計與總計金額的 Plugin 類別範例
class Cart_Plugin_Total extends Cart_Plugin
{
protected $_total = 0;
public function beforeRefreshCart(&$iterator)
{
$this->_total = 0;
}
public function afterRefreshItem($itemKey, &$iterator)
{
$itemData = $iterator[$itemKey];
$iterator[$itemKey]['subtotal'] = $itemData['quantity'] *
$itemData['price'];
$this->_total += $iterator[$itemKey]['subtotal'];
}
public function getValue()
{
return $this->_total;
}
} 取得總金額
// 如何存取 Plugin 的值??
// 如何存取 Plugin 的值??
class Cart {
}
// 如何存取 Plugin 的值??
class Cart {
public function __call($name, $args)
{
}
}
使用 __call 魔術方法來間接呼叫 plugin 的方法這樣一來就不用寫過多的 setter / getter
// 如何存取 Plugin 的值??
class Cart {
public function __call($name, $args)
{
// 設定值$name = preg_replace('/^set/', '', $name);
if (array_key_exists($name, $this->_pluginList)) {
return call_user_func_array(array($this->_pluginList[$name],
'setValue'), $args);
}
}
}
如果方法名為 set 開頭,就呼叫 plugin 的 setValue 方法
// 如何存取 Plugin 的值??
class Cart {
public function __call($name, $args)
{
// 設定值$name = preg_replace('/^set/', '', $name);
if (array_key_exists($name, $this->_pluginList)) {
return call_user_func_array(array($this->_pluginList[$name],
'setValue'), $args);
}
// 取得值$name = preg_replace('/^get/', '', $name);
if (array_key_exists($name, $this->_pluginList)) {
return call_user_func_array(array($this->_pluginList[$name],
'getValue'), $args);
}
}
}
如果方法名為 get 開頭,就呼叫 plugin 的 getValue 方法
// 如何存取 Plugin 的值??
class Cart {
public function __call($name, $args)
{
// 設定值$name = preg_replace('/^set/', '', $name);
if (array_key_exists($name, $this->_pluginList)) {
return call_user_func_array(array($this->_pluginList[$name],
'setValue'), $args);
}
// 取得值$name = preg_replace('/^get/', '', $name);
if (array_key_exists($name, $this->_pluginList)) {
return call_user_func_array(array($this->_pluginList[$name],
'getValue'), $args);
}
return null;
}
} 預設 return null
// 如何存取 Plugin 的值??
class Cart {
public function __call($name, $args)
{
// 設定值$name = preg_replace('/^set/', '', $name);
if (array_key_exists($name, $this->_pluginList)) {
return call_user_func_array(array($this->_pluginList[$name],
'setValue'), $args);
}
// 取得值$name = preg_replace('/^get/', '', $name);
if (array_key_exists($name, $this->_pluginList)) {
return call_user_func_array(array($this->_pluginList[$name],
'getValue'), $args);
}
return null;
}
}
$cart = new Cart();
建立測試用的購物車物件
// 如何存取 Plugin 的值??
class Cart {
public function __call($name, $args)
{
// 設定值$name = preg_replace('/^set/', '', $name);
if (array_key_exists($name, $this->_pluginList)) {
return call_user_func_array(array($this->_pluginList[$name],
'setValue'), $args);
}
// 取得值$name = preg_replace('/^get/', '', $name);
if (array_key_exists($name, $this->_pluginList)) {
return call_user_func_array(array($this->_pluginList[$name],
'getValue'), $args);
}
return null;
}
}
$cart = new Cart();
$cart->registerPlugin('total');
註冊 Total Plugin
// 如何存取 Plugin 的值??
class Cart {
public function __call($name, $args)
{
// 設定值$name = preg_replace('/^set/', '', $name);
if (array_key_exists($name, $this->_pluginList)) {
return call_user_func_array(array($this->_pluginList[$name],
'setValue'), $args);
}
// 取得值$name = preg_replace('/^get/', '', $name);
if (array_key_exists($name, $this->_pluginList)) {
return call_user_func_array(array($this->_pluginList[$name],
'getValue'), $args);
}
return null;
}
}
$cart = new Cart();
$cart->registerPlugin('total');
echo $cart->getTotal();
取得 Total Plugin 的值
Plugin 模式的優點
Plugin 模式的優點
• 不用把過多的功能塞在一個類別上
例如活動、金流、折價券等,可以分開獨立成不同 Plugin
Plugin 模式的優點
• 不用把過多的功能塞在一個類別上
• 新增移除功能非常容易
像是系統要需要新增金流,只要加入新的 Plugin 與畫面即可
Plugin 模式的優點
• 不用把過多的功能塞在一個類別上
• 新增移除功能非常容易
• Plugin 的職責明確
每個 Plugin 只做自己該做的,不會影響到其他程式
Plugin 模式的缺點
Plugin 模式的缺點
• 產生過多的類別
因為每個功能都會需要一個類別
Plugin 模式的缺點
• 產生過多的類別
• 可能會產生重複的資源
相同的資源,卻分別在不同的 Plugin 產生
Plugin 模式的缺點
• 產生過多的類別
• 可能會產生重複的資源
• 一般開發者沒有經過說明不易理解
再加上如果對物件導向不夠熟悉,就更難對這樣的架構進行維護
Plugin 模式要注意的地方
Plugin 模式要注意的地方
• 流程細膩度
切得越細, Plugin 可 Hook 的地方越多,但也會造成開發上的困擾
Plugin 模式要注意的地方
• 流程細膩度
• Plugin 安插的優先順序
例如免運費是需要先知道總金額,所以總金額 Plugin 就要放在前面
Plugin 模式要注意的地方
• 流程細膩度
• Plugin 安插的優先順序
• Plugin 之間的相依性
例如免運費是需要知道總金額,所以免運費 Plugin 相依於總金額 Plugin
Plugin 模式要注意的地方
• 流程細膩度
• Plugin 安插的優先順序
• Plugin 之間的相依性
• Plugin 存取外部資源的部份也要分離
可以用 Dao 來分離資料庫或是外部金流程式,讓測試容易進行
測試購物車
測試購物車
• 用 PHPUnit 來做測試框架
PHPUnit 是目前較常見的測試框架
測試購物車
• 用 PHPUnit 來做測試框架
• 只測試重要的類別
因為測試購物車的目的是在於驗證複雜的計算
測試購物車
• 用 PHPUnit 來做測試框架
• 只測試重要的類別
• 使用假的 Stub 類別來做測試
因為 PHPUnit 提供的 Mock 機制操作上太繁瑣
整合到 MVC
整合到 MVC
• 購物車本身與 MVC 架構無關
任何 Framework 甚至純 PHP 都可以整合
整合到 MVC
• 購物車本身與 MVC 架構無關
• Cart 即 Model
要注意資料庫存取的方式
整合到 MVC
• 購物車本身與 MVC 架構無關
• Cart 即 Model
• Controller 呼叫 Cart 方法
Controller 也只會看到 Cart
整合到 MVC
• 購物車本身與 MVC 架構無關
• Cart 即 Model
• Controller 呼叫 Cart 方法
• View 取得 Cart 資料
所以畫面也可以客制化
問題與討論
問題與討論
• 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要?
問題與討論
• 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要?
• Plugin 模式的應用
問題與討論
• 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要?
• Plugin 模式的應用▫ Zend Framework Dispatcher
問題與討論
• 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要?
• Plugin 模式的應用▫ Zend Framework Dispatcher
▫ 是否可用在 Template 上?
問題與討論
• 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要?
• Plugin 模式的應用▫ Zend Framework Dispatcher
▫ 是否可用在 Template 上?
• 現有架構可否加入 Plugin 模式?
問題與討論
• 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要?
• Plugin 模式的應用▫ Zend Framework Dispatcher
▫ 是否可用在 Template 上?
• 現有架構可否加入 Plugin 模式?
• 實際應用時,效能的考量?
謝謝