More Related Content Similar to 購物車程式架構簡介 (20) 購物車程式架構簡介5. 從程式面來看購物車
購物車
Session
Database
將計算結果存到
Session
加入商品
6. 從程式面來看購物車
購物車
Session
Database
如果要長期保留就
改用 Database
加入商品
7. 從程式面來看購物車
購物車
Session
Database
更新數量可以
透過 AJAX 或是直接
POST 後重新載入,
不要在前端直接用
JavaScript 計算
加入商品 更新數量
8. 從程式面來看購物車
購物車
Session
Database 移除商品與更新數量
是相似的動作
加入商品 更新數量 移除商品
9. 從程式面來看購物車 最後進入金流並完成訂單
購物車
結帳
Session
Database
加入商品 更新數量 移除商品
10. 從程式面來看購物車
購物車
結帳
Session
Database
這些只是購物車的
基本功能而已...
加入商品 更新數量 移除商品
11. 從程式面來看購物車
購物車
結帳
Session
Database
最可怕的是...
加入商品 更新數量 移除商品
12. 從程式面來看購物車
購物車
結帳
Session
Database
加入商品 更新數量 移除商品
17. 促銷活動的種類
• 折價券
• 紅利兌換
• 滿 XXX 免運費
• 買 A 送 B 有點麻煩...如果要拿掉 A 時,也要同時拿掉 B
18. 促銷活動的種類
• 折價券
• 紅利兌換
• 滿 XXX 免運費
• 買A送B
• 紅綠標商品 介面很難做
19. 促銷活動的種類
• 折價券
• 紅利兌換
• 滿 XXX 免運費
• 買A送B
• 紅綠標商品
• 限量搶購 庫存很難搞
20. 促銷活動的種類
• 折價券
• 紅利兌換
• 滿 XXX 免運費
• 買A送B
• 紅綠標商品
• 限量搶購
• ...
更多折磨工程師的行銷手段!!
23. 每個人心裡的 OS
請交給我們一個超多功能的系統!
客戶
請給我們一個安心購物的平台!
消費者
24. 每個人心裡的 OS
請交給我們一個超多功能的系統!
客戶
請給我們一個安心購物的平台!
消費者
請賜死...不對
工程師
25. 每個人心裡的 OS
請交給我們一個超多功能的系統!
客戶
請給我們一個安心購物的平台!
消費者
請賜給我們...一個容易擴充的購物車架構!
工程師
30. 常見的購物車架構
功能 1
Session
購物車 功能 2
Database
功能 3
其他的購物車功能
45. // Storage 類別長什麼樣子?
class Cart_Storage implements ArrayAccess, Countable {
為了能像操作 array 一般地操作 Storage ,
所以實作 ArrayAccess 及 Countable 兩種介面
}
47. // Storage 類別長什麼樣子?
class Cart_Storage implements ArrayAccess, Countable {
protected $_contanier = null;
public function __construct()
{
$this->_container = array();
}
介質初始化,在測試時不需載入資料
}
48. // Storage 類別長什麼樣子?
class Cart_Storage implements ArrayAccess, Countable {
protected $_contanier = null;
public function __construct()
{
$this->_container = array();
}
public function clear()
{
$this->_container = array();
}
清除介質
}
49. // 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 介面提供的方法,
在這裡加入操作介質的程式
}
50. // 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 介面提供的方法,
}
這裡回傳介質的大小
52. // Storage 的 Session 子類別長什麼樣子?
class Cart_Storage_Session {
}
53. // Storage 的 Session 子類別長什麼樣子?
class Cart_Storage_Session extends Cart_Storage {
繼承 Cart_Storage 以沿用處理介質的方法
}
54. // 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 取回資料置入介質
}
55. // 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
}
56. // 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();
}
}
覆蓋清除的方法
59. // 以加入購物車為例:
class Cart {
protected $_storage = null;
public function setStorage(Cart_Storage $storage)
{
$this->_storage = $storage;
}
利用 setStorage 方法讓我們可以
從外部注入 Storage 物件
}
60. // 以加入購物車為例:
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 物件存放
61. // 以加入購物車為例:
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();
建立一個新購物車來測試
62. // 以加入購物車為例:
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 物件
63. // 以加入購物車為例:
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
71. // Dao 類別長什麼樣子?
class Cart_Dao {
public function find($key)
{
return array();
}
public function findAll($keyList)
{
return array();
}
}
用來取得多筆資料用的方法
73. // Dao 的 Db 子類別長什麼樣子?
class Cart_Dao_Product {
}
74. // Dao 的 Db 子類別長什麼樣子?
class Cart_Dao_Product extends Cart_Dao {
繼承 Cart_Dao 類別
}
75. // Dao 的 Db 子類別長什麼樣子?
class Cart_Dao_Product extends Cart_Dao {
public function find($key)
{
覆寫父類別的 find 方法
}
}
76. // 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 對應的商品後就回傳
}
77. // 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 方法
}
}
78. // 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
}
}
79. // 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
}
82. // 以加入購物車為例:
class Cart {
protected $_dao = null;
public function setDao(Cart_Dao $dao)
{
$this->_dao = $dao;
}
利用 setDao 方法讓我們可以
從外部注入 Dao 物件
}
83. // 以加入購物車為例:
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
裡
}
} 沒有的話就做錯誤處理 (這邊省略)
84. // 以加入購物車為例:
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();
建立一個測試用的購物車物件
85. // 以加入購物車為例:
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 物件
86. // 以加入購物車為例:
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());
改從資料庫抓取商品資料
89. Plugin 模式
複合模式
• 在主程式裡呼叫 Plugin
• 有點像 Template Method 和 Observer 的混血
90. Plugin 模式
• 在主程式裡呼叫 Plugin
• 有點像 Template Method 和 Observer 的混血
定義好主流程,其他的實作透過
子類別 Hook 方法來完成
Don't call me, I call you.
92. Plugin 模式
• 在主程式裡呼叫 Plugin
• 有點像 Template Method 和 Observer 的混血
• 最好在設計階段就考慮進去
跟 AOP 很像,
但插入機制是寫死在程式裡;
而 AOP 則是可以動態加入
93. Plugin 模式
• 在主程式裡呼叫 Plugin
• 有點像 Template Method 和 Observer 的混血
• 最好在設計階段就考慮進去
• 與 PoEAA 定義的 Plugin Pattern 是不一樣的
Patterns of Enterprise Application Architecture
by Martin Fowler
94. Plugin 模式
• 在主程式裡呼叫 Plugin
• 有點像 Template Method 和 Observer 的混血
• 最好在設計階段就考慮進去
• 與 PoEAA 定義的 Plugin Pattern 是不一樣的
在不同的運行環境,
執行不一樣的動作
99. Plugin 模式圖解
用戶點選 尋找商品資料
購物車 寫入 Session
頁面連結 並驗證是否
或 Database
加入商品 可以加入
102. Plugin 模式圖解
用戶點選
購物車 頁面連結
加入商品
Plugin 1
Plugin 2
Plugin 3
103. Plugin 模式圖解
這時購物車會呼叫
用戶點選 Plugin
購物車 頁面連結
加入商品
Plugin 1 加入購物車前
Plugin 2 加入購物車前
Plugin 3 加入購物車前
104. Plugin 模式圖解
用戶點選 尋找商品資料
購物車 頁面連結 並驗證是否
加入商品 可以加入
Plugin 1 加入購物車前
Plugin 2 加入購物車前
Plugin 3 加入購物車前
105. Plugin 模式圖解
購物車再次呼叫
用戶點選 尋找商品資料 Plugin
購物車 頁面連結 並驗證是否
加入商品 可以加入
Plugin 1 加入購物車前 加入購物車後
Plugin 2 加入購物車前 加入購物車後
Plugin 3 加入購物車前 加入購物車後
106. Plugin 模式圖解 繼續完成動作
用戶點選 尋找商品資料
購物車 寫入 Session
頁面連結 並驗證是否
或 Database
加入商品 可以加入
Plugin 1 加入購物車前 加入購物車後
Plugin 2 加入購物車前 加入購物車後
Plugin 3 加入購物車前 加入購物車後
110. // 以加入購物車為例:
class Cart {
protected $_pluginList = array();
public function addItem($itemKey) { 用陣列來記住
已載入的 Plugin
// 真正加入購物車
(...略...)
}
}
111. // 以加入購物車為例:
class Cart {
protected $_pluginList = array();
在這裡插入
public function addItem($itemKey) { 加入購物車前的程式
// 加入購物車之前
foreach ($this->_pluginList => $plugin) {
$plugin->beforeAddItem($itemKey);
}
// 真正加入購物車
(...略...)
}
}
112. // 以加入購物車為例:
class Cart {
protected $_pluginList = array();
public function addItem($itemKey) {
// 加入購物車之前
foreach ($this->_pluginList => $plugin) {
$plugin->beforeAddItem($itemKey);
}
// 真正加入購物車 在這裡插入
(...略...)
加入購物車後的程式
// 加入購物車之後
foreach ($this->_pluginList => $plugin) {
$plugin->afterAddItem($itemKey);
}
}
}
116. // Plugin 類別長什麼樣子??
abstract class Cart_Plugin {
protected $_cart = null;
public function __construct(Cart $cart) {
$this->_cart = $cart;
}
購物車屬性可由建構式
的參數帶入
}
117. // 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 方法
(預設為空實作)
119. // 如何加入 Plugin ??
class Cart {
protected $_pluginList = array(); 剛剛的購物車類別
}
120. // 如何加入 Plugin ??
class Cart {
protected $_pluginList = array();
public function registerPlugin($name)
{
加入註冊 Plugin 的方法
}
}
121. // 如何加入 Plugin ??
class Cart {
protected $_pluginList = array();
public function registerPlugin($name)
{
$name = ucfirst($name);
$pluginName = 'Cart_Plugin_' . $name;
轉換成實際 Plugin 的
類別名稱
}
}
122. // 如何加入 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 物件使用
}
123. // 如何加入 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 類別不存在的話,就丟出異常
124. // 如何加入 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();
建立一個新購物車來測試
125. // 如何加入 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
129. // 計算小計與總計金額的 Plugin 類別範例
class Cart_Plugin_Total extends Cart_Plugin
{
protected $_total = 0;
用來記住總金額的屬性
}
130. // 計算小計與總計金額的 Plugin 類別範例
class Cart_Plugin_Total extends Cart_Plugin
{
protected $_total = 0;
public function beforeRefreshCart(&$iterator)
{
$this->_total = 0;
}
更新購物車前先將總金額歸零
}
131. // 計算小計與總計金額的 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'];
}
在更新商品項目後,更新商品小計與總金額
}
132. // 計算小計與總計金額的 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;
}
} 取得總金額
135. // 如何存取 Plugin 的值??
class Cart {
public function __call($name, $args)
{
使用 __call 魔術方法來間接呼叫 plugin 的方法
這樣一來就不用寫過多的 setter / getter
}
}
136. // 如何存取 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 方法
}
}
137. // 如何存取 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 方法
}
}
138. // 如何存取 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
139. // 如何存取 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();
建立測試用的購物車物件
140. // 如何存取 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
141. // 如何存取 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 的值
154. Plugin 模式要注意的地方
• 流程細膩度
• Plugin 安插的優先順序
• Plugin 之間的相依性
• Plugin 存取外部資源的部份也要分離
可以用 Dao 來分離資料庫或是外部金流程
式,讓測試容易進行
158. 測試購物車
• 用 PHPUnit 來做測試框架
• 只測試重要的類別
• 使用假的 Stub 類別來做測試
因為 PHPUnit 提供的 Mock 機制操作上太繁瑣
162. 整合到 MVC
• 購物車本身與 MVC 架構無關
• Cart 即 Model
• Controller 呼叫 Cart 方法
Controller 也只會看到 Cart
163. 整合到 MVC
• 購物車本身與 MVC 架構無關
• Cart 即 Model
• Controller 呼叫 Cart 方法
• View 取得 Cart 資料
所以畫面也可以客制化
167. 問題與討論
• 為什麼 Plugin 需要包含 Cart 物件,但 Storage
和 Dao 不需要?
• Plugin 模式的應用
▫ Zend Framework Dispatcher
168. 問題與討論
• 為什麼 Plugin 需要包含 Cart 物件,但 Storage
和 Dao 不需要?
• Plugin 模式的應用
▫ Zend Framework Dispatcher
▫ 是否可用在 Template 上?
169. 問題與討論
• 為什麼 Plugin 需要包含 Cart 物件,但 Storage
和 Dao 不需要?
• Plugin 模式的應用
▫ Zend Framework Dispatcher
▫ 是否可用在 Template 上?
• 現有架構可否加入 Plugin 模式?
170. 問題與討論
• 為什麼 Plugin 需要包含 Cart 物件,但 Storage
和 Dao 不需要?
• Plugin 模式的應用
▫ Zend Framework Dispatcher
▫ 是否可用在 Template 上?
• 現有架構可否加入 Plugin 模式?
• 實際應用時,效能的考量?