Successfully reported this slideshow.

購物車程式架構簡介

143

Share

Loading in …3
×
1 of 171
1 of 171

More Related Content

Related Books

Free with a 14 day trial from Scribd

See all

購物車程式架構簡介

  1. 1. 購物車程式架構簡介 打造易擴充的系統架構 Jace Ju
  2. 2. 從程式面來看購物車
  3. 3. 從程式面來看購物車 購物車 包含了計算邏輯
  4. 4. 從程式面來看購物車 購物車 收集瀏覽器回傳的資料 加入商品
  5. 5. 從程式面來看購物車 購物車 Session Database 將計算結果存到 Session 加入商品
  6. 6. 從程式面來看購物車 購物車 Session Database 如果要長期保留就 改用 Database 加入商品
  7. 7. 從程式面來看購物車 購物車 Session Database 更新數量可以 透過 AJAX 或是直接 POST 後重新載入, 不要在前端直接用 JavaScript 計算 加入商品 更新數量
  8. 8. 從程式面來看購物車 購物車 Session Database 移除商品與更新數量 是相似的動作 加入商品 更新數量 移除商品
  9. 9. 從程式面來看購物車 最後進入金流並完成訂單 購物車 結帳 Session Database 加入商品 更新數量 移除商品
  10. 10. 從程式面來看購物車 購物車 結帳 Session Database 這些只是購物車的 基本功能而已... 加入商品 更新數量 移除商品
  11. 11. 從程式面來看購物車 購物車 結帳 Session Database 最可怕的是... 加入商品 更新數量 移除商品
  12. 12. 從程式面來看購物車 購物車 結帳 Session Database 加入商品 更新數量 移除商品
  13. 13. 促銷活動的種類
  14. 14. 促銷活動的種類 • 折價券 應該沒有比這個更常見的了
  15. 15. 促銷活動的種類 • 折價券 • 紅利兌換 數學要很好...
  16. 16. 促銷活動的種類 • 折價券 • 紅利兌換 • 滿 XXX 免運費 還算簡單
  17. 17. 促銷活動的種類 • 折價券 • 紅利兌換 • 滿 XXX 免運費 • 買 A 送 B 有點麻煩...如果要拿掉 A 時,也要同時拿掉 B
  18. 18. 促銷活動的種類 • 折價券 • 紅利兌換 • 滿 XXX 免運費 • 買A送B • 紅綠標商品 介面很難做
  19. 19. 促銷活動的種類 • 折價券 • 紅利兌換 • 滿 XXX 免運費 • 買A送B • 紅綠標商品 • 限量搶購 庫存很難搞
  20. 20. 促銷活動的種類 • 折價券 • 紅利兌換 • 滿 XXX 免運費 • 買A送B • 紅綠標商品 • 限量搶購 • ... 更多折磨工程師的行銷手段!!
  21. 21. 每個人心裡的 OS
  22. 22. 每個人心裡的 OS 請交給我們一個超多功能的系統! 客戶
  23. 23. 每個人心裡的 OS 請交給我們一個超多功能的系統! 客戶 請給我們一個安心購物的平台! 消費者
  24. 24. 每個人心裡的 OS 請交給我們一個超多功能的系統! 客戶 請給我們一個安心購物的平台! 消費者 請賜死...不對 工程師
  25. 25. 每個人心裡的 OS 請交給我們一個超多功能的系統! 客戶 請給我們一個安心購物的平台! 消費者 請賜給我們...一個容易擴充的購物車架構! 工程師
  26. 26. 常見的購物車架構
  27. 27. 常見的購物車架構 購物車
  28. 28. 常見的購物車架構 儲存購物車中的商品資訊 Session 購物車
  29. 29. 常見的購物車架構 Session 購物車 Database 在資料庫中尋找商品
  30. 30. 常見的購物車架構 功能 1 Session 購物車 功能 2 Database 功能 3 其他的購物車功能
  31. 31. 如何讓購物車容易測試與擴充?
  32. 32. 如何讓購物車容易測試與擴充? • 主架構只保留最基本的計算邏輯 簡化架構以求測試容易
  33. 33. 如何讓購物車容易測試與擴充? • 主架構只保留最基本的計算邏輯 ▫ 抽離 Session 與 Database
  34. 34. 如何讓購物車容易測試與擴充? • 主架構只保留最基本的計算邏輯 ▫ 抽離 Session 與 Database ▫ 加入可擴充的機制,讓經常變動的功能變成 Plugin
  35. 35. 如何讓購物車容易測試與擴充? • 主架構只保留最基本的計算邏輯 ▫ 抽離 Session 與 Database ▫ 加入可擴充的機制,讓經常變動的功能變成 Plugin • 測試時 儘量避免測試要依賴系統
  36. 36. 如何讓購物車容易測試與擴充? • 主架構只保留最基本的計算邏輯 ▫ 抽離 Session 與 Database ▫ 加入可擴充的機制,讓經常變動的功能變成 Plugin • 測試時 ▫ Session 改用 Storage
  37. 37. 如何讓購物車容易測試與擴充? • 主架構只保留最基本的計算邏輯 ▫ 抽離 Session 與 Database ▫ 加入可擴充的機制,讓經常變動的功能變成 Plugin • 測試時 ▫ Session 改用 Storage ▫ Database 改用 Dao
  38. 38. 如何讓購物車容易測試與擴充? • 主架構只保留最基本的計算邏輯 ▫ 抽離 Session 與 Database ▫ 加入可擴充的機制,讓經常變動的功能變成 Plugin • 測試時 ▫ Session 改用 Storage ▫ Database 改用 Dao ▫ 主架構與 Plugin 分開測試
  39. 39. Storage
  40. 40. Storage • 用最單純的 Array 來做為介質 避免自動測試工具 不支援 Session
  41. 41. Storage • 用最單純的 Array 來做為介質 • 提供操作介質的介面 將操作抽象化 就可以封裝介質的不同
  42. 42. Storage • 用最單純的 Array 來做為介質 • 提供操作介質的介面 • 真正寫入 Session 的部份交給子類別 在實際運作時,再改用子類別
  43. 43. // Storage 類別長什麼樣子?
  44. 44. // Storage 類別長什麼樣子? class Cart_Storage { }
  45. 45. // Storage 類別長什麼樣子? class Cart_Storage implements ArrayAccess, Countable { 為了能像操作 array 一般地操作 Storage , 所以實作 ArrayAccess 及 Countable 兩種介面 }
  46. 46. // Storage 類別長什麼樣子? class Cart_Storage implements ArrayAccess, Countable { protected $_contanier = null; 儲存介質 }
  47. 47. // Storage 類別長什麼樣子? class Cart_Storage implements ArrayAccess, Countable { protected $_contanier = null; public function __construct() { $this->_container = array(); } 介質初始化,在測試時不需載入資料 }
  48. 48. // Storage 類別長什麼樣子? class Cart_Storage implements ArrayAccess, Countable { protected $_contanier = null; public function __construct() { $this->_container = array(); } public function clear() { $this->_container = array(); } 清除介質 }
  49. 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. 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 介面提供的方法, } 這裡回傳介質的大小
  51. 51. // Storage 的 Session 子類別長什麼樣子?
  52. 52. // Storage 的 Session 子類別長什麼樣子? class Cart_Storage_Session { }
  53. 53. // Storage 的 Session 子類別長什麼樣子? class Cart_Storage_Session extends Cart_Storage { 繼承 Cart_Storage 以沿用處理介質的方法 }
  54. 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. 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. 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(); } } 覆蓋清除的方法
  57. 57. // 以加入購物車為例:
  58. 58. // 以加入購物車為例: class Cart { }
  59. 59. // 以加入購物車為例: class Cart { protected $_storage = null; public function setStorage(Cart_Storage $storage) { $this->_storage = $storage; } 利用 setStorage 方法讓我們可以 從外部注入 Storage 物件 }
  60. 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. 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. 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. 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
  64. 64. Dao
  65. 65. Dao • 目的是把 Database 與主架構做分離 這樣就不再依賴資料庫
  66. 66. Dao • 目的是把 Database 與主架構做分離 • 僅提供 API ,不關心資料從何處取得 我們只關心回傳給購物 車的格式是否正確
  67. 67. Dao • 目的是把 Database 與主架構做分離 • 僅提供 API ,不關心資料從何處取得 • 真正存取 Database 的部份交給子類別 在實際運作時,再改用子類別
  68. 68. // Dao 類別長什麼樣子?
  69. 69. // Dao 類別長什麼樣子? class Cart_Dao { }
  70. 70. // Dao 類別長什麼樣子? class Cart_Dao { public function find($key) { return array(); } 用來取得一筆資料用的方法 }
  71. 71. // Dao 類別長什麼樣子? class Cart_Dao { public function find($key) { return array(); } public function findAll($keyList) { return array(); } } 用來取得多筆資料用的方法
  72. 72. // Dao 的 Db 子類別長什麼樣子?
  73. 73. // Dao 的 Db 子類別長什麼樣子? class Cart_Dao_Product { }
  74. 74. // Dao 的 Db 子類別長什麼樣子? class Cart_Dao_Product extends Cart_Dao { 繼承 Cart_Dao 類別 }
  75. 75. // Dao 的 Db 子類別長什麼樣子? class Cart_Dao_Product extends Cart_Dao { public function find($key) { 覆寫父類別的 find 方法 } }
  76. 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. 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. 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. 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 }
  80. 80. // 以加入購物車為例:
  81. 81. // 以加入購物車為例: class Cart { }
  82. 82. // 以加入購物車為例: class Cart { protected $_dao = null; public function setDao(Cart_Dao $dao) { $this->_dao = $dao; } 利用 setDao 方法讓我們可以 從外部注入 Dao 物件 }
  83. 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. 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. 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. 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()); 改從資料庫抓取商品資料
  87. 87. Plugin 模式
  88. 88. Plugin 模式 • 在主程式裡呼叫 Plugin 流程還是由主程式控制 Plugin 只負責提供額外功能
  89. 89. Plugin 模式 複合模式 • 在主程式裡呼叫 Plugin • 有點像 Template Method 和 Observer 的混血
  90. 90. Plugin 模式 • 在主程式裡呼叫 Plugin • 有點像 Template Method 和 Observer 的混血 定義好主流程,其他的實作透過 子類別 Hook 方法來完成 Don't call me, I call you.
  91. 91. Plugin 模式 • 在主程式裡呼叫 Plugin • 有點像 Template Method 和 Observer 的混血 一旦有動作,會立即通知 訂閱者做改變
  92. 92. Plugin 模式 • 在主程式裡呼叫 Plugin • 有點像 Template Method 和 Observer 的混血 • 最好在設計階段就考慮進去 跟 AOP 很像, 但插入機制是寫死在程式裡; 而 AOP 則是可以動態加入
  93. 93. Plugin 模式 • 在主程式裡呼叫 Plugin • 有點像 Template Method 和 Observer 的混血 • 最好在設計階段就考慮進去 • 與 PoEAA 定義的 Plugin Pattern 是不一樣的 Patterns of Enterprise Application Architecture by Martin Fowler
  94. 94. Plugin 模式 • 在主程式裡呼叫 Plugin • 有點像 Template Method 和 Observer 的混血 • 最好在設計階段就考慮進去 • 與 PoEAA 定義的 Plugin Pattern 是不一樣的 在不同的運行環境, 執行不一樣的動作
  95. 95. Plugin 模式圖解
  96. 96. Plugin 模式圖解 購物車
  97. 97. Plugin 模式圖解 用戶點選 購物車 頁面連結 加入商品
  98. 98. Plugin 模式圖解 用戶點選 尋找商品資料 購物車 頁面連結 並驗證是否 加入商品 可以加入
  99. 99. Plugin 模式圖解 用戶點選 尋找商品資料 購物車 寫入 Session 頁面連結 並驗證是否 或 Database 加入商品 可以加入
  100. 100. Plugin 模式圖解 購物車
  101. 101. Plugin 模式圖解 購物車 加入 Plugin 機制 Plugin 1 Plugin 2 Plugin 3
  102. 102. Plugin 模式圖解 用戶點選 購物車 頁面連結 加入商品 Plugin 1 Plugin 2 Plugin 3
  103. 103. Plugin 模式圖解 這時購物車會呼叫 用戶點選 Plugin 購物車 頁面連結 加入商品 Plugin 1 加入購物車前 Plugin 2 加入購物車前 Plugin 3 加入購物車前
  104. 104. Plugin 模式圖解 用戶點選 尋找商品資料 購物車 頁面連結 並驗證是否 加入商品 可以加入 Plugin 1 加入購物車前 Plugin 2 加入購物車前 Plugin 3 加入購物車前
  105. 105. Plugin 模式圖解 購物車再次呼叫 用戶點選 尋找商品資料 Plugin 購物車 頁面連結 並驗證是否 加入商品 可以加入 Plugin 1 加入購物車前 加入購物車後 Plugin 2 加入購物車前 加入購物車後 Plugin 3 加入購物車前 加入購物車後
  106. 106. Plugin 模式圖解 繼續完成動作 用戶點選 尋找商品資料 購物車 寫入 Session 頁面連結 並驗證是否 或 Database 加入商品 可以加入 Plugin 1 加入購物車前 加入購物車後 Plugin 2 加入購物車前 加入購物車後 Plugin 3 加入購物車前 加入購物車後
  107. 107. // 以加入購物車為例:
  108. 108. // 以加入購物車為例: class Cart { 購物車類別 }
  109. 109. // 以加入購物車為例: class Cart { public function addItem($itemKey) { 加入購物車方法 // 真正加入購物車 (...略...) } }
  110. 110. // 以加入購物車為例: class Cart { protected $_pluginList = array(); public function addItem($itemKey) { 用陣列來記住 已載入的 Plugin // 真正加入購物車 (...略...) } }
  111. 111. // 以加入購物車為例: class Cart { protected $_pluginList = array(); 在這裡插入 public function addItem($itemKey) { 加入購物車前的程式 // 加入購物車之前 foreach ($this->_pluginList => $plugin) { $plugin->beforeAddItem($itemKey); } // 真正加入購物車 (...略...) } }
  112. 112. // 以加入購物車為例: class Cart { protected $_pluginList = array(); public function addItem($itemKey) { // 加入購物車之前 foreach ($this->_pluginList => $plugin) { $plugin->beforeAddItem($itemKey); } // 真正加入購物車 在這裡插入 (...略...) 加入購物車後的程式 // 加入購物車之後 foreach ($this->_pluginList => $plugin) { $plugin->afterAddItem($itemKey); } } }
  113. 113. // Plugin 類別長什麼樣子??
  114. 114. // Plugin 類別長什麼樣子?? abstract class Cart_Plugin { 首先要有一個抽象類別 }
  115. 115. // Plugin 類別長什麼樣子?? abstract class Cart_Plugin { protected $_cart = null; 為了可直接取得購物車的資訊 所以有一個購物車屬性 }
  116. 116. // Plugin 類別長什麼樣子?? abstract class Cart_Plugin { protected $_cart = null; public function __construct(Cart $cart) { $this->_cart = $cart; } 購物車屬性可由建構式 的參數帶入 }
  117. 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 方法 (預設為空實作)
  118. 118. // 如何加入 Plugin ??
  119. 119. // 如何加入 Plugin ?? class Cart { protected $_pluginList = array(); 剛剛的購物車類別 }
  120. 120. // 如何加入 Plugin ?? class Cart { protected $_pluginList = array(); public function registerPlugin($name) { 加入註冊 Plugin 的方法 } }
  121. 121. // 如何加入 Plugin ?? class Cart { protected $_pluginList = array(); public function registerPlugin($name) { $name = ucfirst($name); $pluginName = 'Cart_Plugin_' . $name; 轉換成實際 Plugin 的 類別名稱 } }
  122. 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. 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. 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. 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
  126. 126. // 計算小計與總計金額的 Plugin 類別範例
  127. 127. // 計算小計與總計金額的 Plugin 類別範例 class Cart_Plugin_Total { }
  128. 128. // 計算小計與總計金額的 Plugin 類別範例 class Cart_Plugin_Total extends Cart_Plugin { 繼承抽象的 Cart_Plugin 類別 }
  129. 129. // 計算小計與總計金額的 Plugin 類別範例 class Cart_Plugin_Total extends Cart_Plugin { protected $_total = 0; 用來記住總金額的屬性 }
  130. 130. // 計算小計與總計金額的 Plugin 類別範例 class Cart_Plugin_Total extends Cart_Plugin { protected $_total = 0; public function beforeRefreshCart(&$iterator) { $this->_total = 0; } 更新購物車前先將總金額歸零 }
  131. 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. 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; } } 取得總金額
  133. 133. // 如何存取 Plugin 的值??
  134. 134. // 如何存取 Plugin 的值?? class Cart { }
  135. 135. // 如何存取 Plugin 的值?? class Cart { public function __call($name, $args) { 使用 __call 魔術方法來間接呼叫 plugin 的方法 這樣一來就不用寫過多的 setter / getter } }
  136. 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. 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. 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. 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. 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. 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 的值
  142. 142. Plugin 模式的優點
  143. 143. Plugin 模式的優點 • 不用把過多的功能塞在一個類別上 例如活動、金流、折價券等, 可以分開獨立成不同 Plugin
  144. 144. Plugin 模式的優點 • 不用把過多的功能塞在一個類別上 • 新增移除功能非常容易 像是系統要需要新增金流, 只要加入新的 Plugin 與畫面即可
  145. 145. Plugin 模式的優點 • 不用把過多的功能塞在一個類別上 • 新增移除功能非常容易 • Plugin 的職責明確 每個 Plugin 只做自己該做的, 不會影響到其他程式
  146. 146. Plugin 模式的缺點
  147. 147. Plugin 模式的缺點 • 產生過多的類別 因為每個功能都會需要一個類別
  148. 148. Plugin 模式的缺點 • 產生過多的類別 • 可能會產生重複的資源 相同的資源,卻分別在不同的 Plugin 產生
  149. 149. Plugin 模式的缺點 • 產生過多的類別 • 可能會產生重複的資源 • 一般開發者沒有經過說明不易理解 再加上如果對物件導向不夠熟悉, 就更難對這樣的架構進行維護
  150. 150. Plugin 模式要注意的地方
  151. 151. Plugin 模式要注意的地方 • 流程細膩度 切得越細, Plugin 可 Hook 的地方越多, 但也會造成開發上的困擾
  152. 152. Plugin 模式要注意的地方 • 流程細膩度 • Plugin 安插的優先順序 例如免運費是需要先知道總金額, 所以總金額 Plugin 就要放在前面
  153. 153. Plugin 模式要注意的地方 • 流程細膩度 • Plugin 安插的優先順序 • Plugin 之間的相依性 例如免運費是需要知道總金額, 所以免運費 Plugin 相依於總金額 Plugin
  154. 154. Plugin 模式要注意的地方 • 流程細膩度 • Plugin 安插的優先順序 • Plugin 之間的相依性 • Plugin 存取外部資源的部份也要分離 可以用 Dao 來分離資料庫或是外部金流程 式,讓測試容易進行
  155. 155. 測試購物車
  156. 156. 測試購物車 • 用 PHPUnit 來做測試框架 PHPUnit 是目前較常見的測試框架
  157. 157. 測試購物車 • 用 PHPUnit 來做測試框架 • 只測試重要的類別 因為測試購物車的目的是在於驗證複雜的計算
  158. 158. 測試購物車 • 用 PHPUnit 來做測試框架 • 只測試重要的類別 • 使用假的 Stub 類別來做測試 因為 PHPUnit 提供的 Mock 機制操作上太繁瑣
  159. 159. 整合到 MVC
  160. 160. 整合到 MVC • 購物車本身與 MVC 架構無關 任何 Framework 甚至純 PHP 都可以整合
  161. 161. 整合到 MVC • 購物車本身與 MVC 架構無關 • Cart 即 Model 要注意資料庫存取的方式
  162. 162. 整合到 MVC • 購物車本身與 MVC 架構無關 • Cart 即 Model • Controller 呼叫 Cart 方法 Controller 也只會看到 Cart
  163. 163. 整合到 MVC • 購物車本身與 MVC 架構無關 • Cart 即 Model • Controller 呼叫 Cart 方法 • View 取得 Cart 資料 所以畫面也可以客制化
  164. 164. 問題與討論
  165. 165. 問題與討論 • 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要?
  166. 166. 問題與討論 • 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要? • Plugin 模式的應用
  167. 167. 問題與討論 • 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要? • Plugin 模式的應用 ▫ Zend Framework Dispatcher
  168. 168. 問題與討論 • 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要? • Plugin 模式的應用 ▫ Zend Framework Dispatcher ▫ 是否可用在 Template 上?
  169. 169. 問題與討論 • 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要? • Plugin 模式的應用 ▫ Zend Framework Dispatcher ▫ 是否可用在 Template 上? • 現有架構可否加入 Plugin 模式?
  170. 170. 問題與討論 • 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要? • Plugin 模式的應用 ▫ Zend Framework Dispatcher ▫ 是否可用在 Template 上? • 現有架構可否加入 Plugin 模式? • 實際應用時,效能的考量?
  171. 171. 謝謝

×