171
購物車程式架構簡介 打造易擴充的系統架構 Jace Ju

購物車程式架構簡介

  • Upload
    jace-ju

  • View
    23.395

  • Download
    2

Embed Size (px)

DESCRIPTION

介紹 Plugin 機制

Citation preview

Page 1: 購物車程式架構簡介

購物車程式架構簡介打造易擴充的系統架構Jace Ju

Page 2: 購物車程式架構簡介

從程式面來看購物車

Page 3: 購物車程式架構簡介

購物車

從程式面來看購物車

包含了計算邏輯

Page 4: 購物車程式架構簡介

購物車

從程式面來看購物車

加入商品

收集瀏覽器回傳的資料

Page 5: 購物車程式架構簡介

購物車

從程式面來看購物車

加入商品

SessionDatabase

將計算結果存到Session

Page 6: 購物車程式架構簡介

購物車

從程式面來看購物車

加入商品

SessionDatabase

如果要長期保留就改用 Database

Page 7: 購物車程式架構簡介

購物車

從程式面來看購物車

加入商品 更新數量

SessionDatabase

更新數量可以透過 AJAX 或是直接POST 後重新載入,不要在前端直接用JavaScript 計算

Page 8: 購物車程式架構簡介

購物車

從程式面來看購物車

加入商品 更新數量 移除商品

SessionDatabase 移除商品與更新數量

是相似的動作

Page 9: 購物車程式架構簡介

購物車

從程式面來看購物車

加入商品 更新數量 移除商品

SessionDatabase

結帳

最後進入金流並完成訂單

Page 10: 購物車程式架構簡介

購物車

從程式面來看購物車

加入商品 更新數量 移除商品

SessionDatabase

結帳

這些只是購物車的基本功能而已...

Page 11: 購物車程式架構簡介

購物車

從程式面來看購物車

加入商品 更新數量 移除商品

SessionDatabase

結帳

最可怕的是...

Page 12: 購物車程式架構簡介

購物車

從程式面來看購物車

加入商品 更新數量 移除商品

SessionDatabase

結帳

Page 13: 購物車程式架構簡介

促銷活動的種類

Page 14: 購物車程式架構簡介

促銷活動的種類

• 折價券 應該沒有比這個更常見的了

Page 15: 購物車程式架構簡介

促銷活動的種類

• 折價券

• 紅利兌換 數學要很好...

Page 16: 購物車程式架構簡介

促銷活動的種類

• 折價券

• 紅利兌換

• 滿 XXX 免運費還算簡單

Page 17: 購物車程式架構簡介

促銷活動的種類

• 折價券

• 紅利兌換

• 滿 XXX 免運費

• 買 A 送 B 有點麻煩...如果要拿掉 A 時,也要同時拿掉 B

Page 18: 購物車程式架構簡介

促銷活動的種類

• 折價券

• 紅利兌換

• 滿 XXX 免運費

• 買 A 送 B

• 紅綠標商品介面很難做

Page 19: 購物車程式架構簡介

促銷活動的種類

• 折價券

• 紅利兌換

• 滿 XXX 免運費

• 買 A 送 B

• 紅綠標商品

• 限量搶購 庫存很難搞

Page 20: 購物車程式架構簡介

促銷活動的種類

• 折價券

• 紅利兌換

• 滿 XXX 免運費

• 買 A 送 B

• 紅綠標商品

• 限量搶購

• ...更多折磨工程師的行銷手段!!

Page 21: 購物車程式架構簡介

每個人心裡的 OS

Page 22: 購物車程式架構簡介

每個人心裡的 OS

客戶

請交給我們一個超多功能的系統!

Page 23: 購物車程式架構簡介

每個人心裡的 OS

客戶

請交給我們一個超多功能的系統!

消費者

請給我們一個安心購物的平台!

Page 24: 購物車程式架構簡介

每個人心裡的 OS

客戶

請交給我們一個超多功能的系統!

消費者

請給我們一個安心購物的平台!

請賜死...不對

工程師

Page 25: 購物車程式架構簡介

每個人心裡的 OS

客戶

請交給我們一個超多功能的系統!

消費者

請給我們一個安心購物的平台!

請賜給我們...一個容易擴充的購物車架構!

工程師

Page 26: 購物車程式架構簡介

常見的購物車架構

Page 27: 購物車程式架構簡介

常見的購物車架構

購物車

Page 28: 購物車程式架構簡介

常見的購物車架構

購物車

Session

儲存購物車中的商品資訊

Page 29: 購物車程式架構簡介

常見的購物車架構

購物車

Session

Database

在資料庫中尋找商品

Page 30: 購物車程式架構簡介

常見的購物車架構

購物車

Session

Database

功能 1

功能 2

功能 3

其他的購物車功能

Page 31: 購物車程式架構簡介

如何讓購物車容易測試與擴充?

Page 32: 購物車程式架構簡介

如何讓購物車容易測試與擴充?

• 主架構只保留最基本的計算邏輯

簡化架構以求測試容易

Page 33: 購物車程式架構簡介

如何讓購物車容易測試與擴充?

• 主架構只保留最基本的計算邏輯▫ 抽離 Session 與 Database

Page 34: 購物車程式架構簡介

如何讓購物車容易測試與擴充?

• 主架構只保留最基本的計算邏輯▫ 抽離 Session 與 Database

▫ 加入可擴充的機制,讓經常變動的功能變成 Plugin

Page 35: 購物車程式架構簡介

如何讓購物車容易測試與擴充?

• 主架構只保留最基本的計算邏輯▫ 抽離 Session 與 Database

▫ 加入可擴充的機制,讓經常變動的功能變成 Plugin

• 測試時儘量避免測試要依賴系統

Page 36: 購物車程式架構簡介

如何讓購物車容易測試與擴充?

• 主架構只保留最基本的計算邏輯▫ 抽離 Session 與 Database

▫ 加入可擴充的機制,讓經常變動的功能變成 Plugin

• 測試時▫ Session 改用 Storage

Page 37: 購物車程式架構簡介

如何讓購物車容易測試與擴充?

• 主架構只保留最基本的計算邏輯▫ 抽離 Session 與 Database

▫ 加入可擴充的機制,讓經常變動的功能變成 Plugin

• 測試時▫ Session 改用 Storage

▫ Database 改用 Dao

Page 38: 購物車程式架構簡介

如何讓購物車容易測試與擴充?

• 主架構只保留最基本的計算邏輯▫ 抽離 Session 與 Database

▫ 加入可擴充的機制,讓經常變動的功能變成 Plugin

• 測試時▫ Session 改用 Storage

▫ Database 改用 Dao

▫ 主架構與 Plugin 分開測試

Page 39: 購物車程式架構簡介

Storage

Page 40: 購物車程式架構簡介

Storage

• 用最單純的 Array 來做為介質 避免自動測試工具不支援 Session

Page 41: 購物車程式架構簡介

Storage

• 用最單純的 Array 來做為介質

• 提供操作介質的介面 將操作抽象化就可以封裝介質的不同

Page 42: 購物車程式架構簡介

Storage

• 用最單純的 Array 來做為介質

• 提供操作介質的介面

• 真正寫入 Session 的部份交給子類別

在實際運作時,再改用子類別

Page 43: 購物車程式架構簡介

// Storage 類別長什麼樣子?

Page 44: 購物車程式架構簡介

// Storage 類別長什麼樣子?

class Cart_Storage {

}

Page 45: 購物車程式架構簡介

// Storage 類別長什麼樣子?

class Cart_Storage implements ArrayAccess, Countable {

}

為了能像操作 array 一般地操作 Storage ,所以實作 ArrayAccess 及 Countable 兩種介面

Page 46: 購物車程式架構簡介

// Storage 類別長什麼樣子?

class Cart_Storage implements ArrayAccess, Countable {

protected $_contanier = null;

}

儲存介質

Page 47: 購物車程式架構簡介

// Storage 類別長什麼樣子?

class Cart_Storage implements ArrayAccess, Countable {

protected $_contanier = null;

public function __construct()

{

$this->_container = array();

}

}

介質初始化,在測試時不需載入資料

Page 48: 購物車程式架構簡介

// Storage 類別長什麼樣子?

class Cart_Storage implements ArrayAccess, Countable {

protected $_contanier = null;

public function __construct()

{

$this->_container = array();

}

public function clear()

{

$this->_container = array();

}

}

清除介質

Page 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 介面提供的方法,在這裡加入操作介質的程式

Page 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 介面提供的方法,

這裡回傳介質的大小

Page 51: 購物車程式架構簡介

// Storage 的 Session 子類別長什麼樣子?

Page 52: 購物車程式架構簡介

// Storage 的 Session 子類別長什麼樣子?

class Cart_Storage_Session {

}

Page 53: 購物車程式架構簡介

// Storage 的 Session 子類別長什麼樣子?

class Cart_Storage_Session extends Cart_Storage {

}

繼承 Cart_Storage 以沿用處理介質的方法

Page 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 取回資料置入介質

Page 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

Page 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();

}

}覆蓋清除的方法

Page 57: 購物車程式架構簡介

// 以加入購物車為例:

Page 58: 購物車程式架構簡介

// 以加入購物車為例:

class Cart {

}

Page 59: 購物車程式架構簡介

// 以加入購物車為例:

class Cart {

protected $_storage = null;

public function setStorage(Cart_Storage $storage)

{

$this->_storage = $storage;

}

}

利用 setStorage 方法讓我們可以從外部注入 Storage 物件

Page 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 物件存放

Page 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();

建立一個新購物車來測試

Page 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 物件

Page 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

Page 64: 購物車程式架構簡介

Dao

Page 65: 購物車程式架構簡介

Dao

• 目的是把 Database 與主架構做分離

這樣就不再依賴資料庫

Page 66: 購物車程式架構簡介

Dao

• 目的是把 Database 與主架構做分離

• 僅提供 API ,不關心資料從何處取得

我們只關心回傳給購物車的格式是否正確

Page 67: 購物車程式架構簡介

Dao

• 目的是把 Database 與主架構做分離

• 僅提供 API ,不關心資料從何處取得

• 真正存取 Database 的部份交給子類別

在實際運作時,再改用子類別

Page 68: 購物車程式架構簡介

// Dao 類別長什麼樣子?

Page 69: 購物車程式架構簡介

// Dao 類別長什麼樣子?

class Cart_Dao {

}

Page 70: 購物車程式架構簡介

// Dao 類別長什麼樣子?

class Cart_Dao {

public function find($key)

{

return array();

}

}

用來取得一筆資料用的方法

Page 71: 購物車程式架構簡介

// Dao 類別長什麼樣子?

class Cart_Dao {

public function find($key)

{

return array();

}

public function findAll($keyList)

{

return array();

}

} 用來取得多筆資料用的方法

Page 72: 購物車程式架構簡介

// Dao 的 Db 子類別長什麼樣子?

Page 73: 購物車程式架構簡介

// Dao 的 Db 子類別長什麼樣子?

class Cart_Dao_Product {

}

Page 74: 購物車程式架構簡介

// Dao 的 Db 子類別長什麼樣子?

class Cart_Dao_Product extends Cart_Dao {

}

繼承 Cart_Dao 類別

Page 75: 購物車程式架構簡介

// Dao 的 Db 子類別長什麼樣子?

class Cart_Dao_Product extends Cart_Dao {

public function find($key)

{

}

}

覆寫父類別的 find 方法

Page 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 對應的商品後就回傳

Page 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 方法

Page 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

Page 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

Page 80: 購物車程式架構簡介

// 以加入購物車為例:

Page 81: 購物車程式架構簡介

// 以加入購物車為例:

class Cart {

}

Page 82: 購物車程式架構簡介

// 以加入購物車為例:

class Cart {

protected $_dao = null;

public function setDao(Cart_Dao $dao)

{

$this->_dao = $dao;

}

}

利用 setDao 方法讓我們可以從外部注入 Dao 物件

Page 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 裡沒有的話就做錯誤處理 (這邊省略)

Page 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();

建立一個測試用的購物車物件

Page 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 物件

Page 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());

改從資料庫抓取商品資料

Page 87: 購物車程式架構簡介

Plugin 模式

Page 88: 購物車程式架構簡介

Plugin 模式

• 在主程式裡呼叫 Plugin 流程還是由主程式控制Plugin 只負責提供額外功能

Page 89: 購物車程式架構簡介

Plugin 模式

• 在主程式裡呼叫 Plugin

• 有點像 Template Method 和 Observer 的混血

複合模式

Page 90: 購物車程式架構簡介

Plugin 模式

• 在主程式裡呼叫 Plugin

• 有點像 Template Method 和 Observer 的混血

定義好主流程,其他的實作透過子類別 Hook 方法來完成

Don't call me, I call you.

Page 91: 購物車程式架構簡介

Plugin 模式

• 在主程式裡呼叫 Plugin

• 有點像 Template Method 和 Observer 的混血

一旦有動作,會立即通知訂閱者做改變

Page 92: 購物車程式架構簡介

Plugin 模式

• 在主程式裡呼叫 Plugin

• 有點像 Template Method 和 Observer 的混血

• 最好在設計階段就考慮進去

跟 AOP 很像,但插入機制是寫死在程式裡;而 AOP 則是可以動態加入

Page 93: 購物車程式架構簡介

Plugin 模式

• 在主程式裡呼叫 Plugin

• 有點像 Template Method 和 Observer 的混血

• 最好在設計階段就考慮進去

• 與 PoEAA 定義的 Plugin Pattern 是不一樣的

Patterns of Enterprise Application Architecture

by Martin Fowler

Page 94: 購物車程式架構簡介

Plugin 模式

• 在主程式裡呼叫 Plugin

• 有點像 Template Method 和 Observer 的混血

• 最好在設計階段就考慮進去

• 與 PoEAA 定義的 Plugin Pattern 是不一樣的

在不同的運行環境,執行不一樣的動作

Page 95: 購物車程式架構簡介

Plugin 模式圖解

Page 96: 購物車程式架構簡介

Plugin 模式圖解

購物車

Page 97: 購物車程式架構簡介

Plugin 模式圖解

用戶點選頁面連結加入商品

購物車

Page 98: 購物車程式架構簡介

Plugin 模式圖解

用戶點選頁面連結加入商品

尋找商品資料並驗證是否可以加入

購物車

Page 99: 購物車程式架構簡介

Plugin 模式圖解

用戶點選頁面連結加入商品

尋找商品資料並驗證是否可以加入

寫入 Session或 Database

購物車

Page 100: 購物車程式架構簡介

Plugin 模式圖解

購物車

Page 101: 購物車程式架構簡介

Plugin 模式圖解

購物車

Plugin 1

Plugin 2

Plugin 3

加入 Plugin 機制

Page 102: 購物車程式架構簡介

Plugin 模式圖解

用戶點選頁面連結加入商品

購物車

Plugin 1

Plugin 2

Plugin 3

Page 103: 購物車程式架構簡介

Plugin 模式圖解

用戶點選頁面連結加入商品

加入購物車前

加入購物車前

加入購物車前

購物車

Plugin 1

Plugin 2

Plugin 3

這時購物車會呼叫Plugin

Page 104: 購物車程式架構簡介

Plugin 模式圖解

用戶點選頁面連結加入商品

尋找商品資料並驗證是否可以加入

加入購物車前

加入購物車前

加入購物車前

購物車

Plugin 1

Plugin 2

Plugin 3

Page 105: 購物車程式架構簡介

Plugin 模式圖解

用戶點選頁面連結加入商品

尋找商品資料並驗證是否可以加入

加入購物車前 加入購物車後

加入購物車前 加入購物車後

加入購物車前 加入購物車後

購物車

Plugin 1

Plugin 2

Plugin 3

購物車再次呼叫Plugin

Page 106: 購物車程式架構簡介

Plugin 模式圖解

用戶點選頁面連結加入商品

尋找商品資料並驗證是否可以加入

寫入 Session或 Database

加入購物車前 加入購物車後

加入購物車前 加入購物車後

加入購物車前 加入購物車後

購物車

Plugin 1

Plugin 2

Plugin 3

繼續完成動作

Page 107: 購物車程式架構簡介

// 以加入購物車為例:

Page 108: 購物車程式架構簡介

// 以加入購物車為例:

class Cart {

}

購物車類別

Page 109: 購物車程式架構簡介

// 以加入購物車為例:

class Cart {

public function addItem($itemKey) {

// 真正加入購物車(...略...)

}

}

加入購物車方法

Page 110: 購物車程式架構簡介

// 以加入購物車為例:

class Cart {

protected $_pluginList = array();

public function addItem($itemKey) {

// 真正加入購物車(...略...)

}

}

用陣列來記住已載入的 Plugin

Page 111: 購物車程式架構簡介

// 以加入購物車為例:

class Cart {

protected $_pluginList = array();

public function addItem($itemKey) {

// 加入購物車之前foreach ($this->_pluginList => $plugin) {

$plugin->beforeAddItem($itemKey);

}

// 真正加入購物車(...略...)

}

}

在這裡插入加入購物車前的程式

Page 112: 購物車程式架構簡介

// 以加入購物車為例:

class Cart {

protected $_pluginList = array();

public function addItem($itemKey) {

// 加入購物車之前foreach ($this->_pluginList => $plugin) {

$plugin->beforeAddItem($itemKey);

}

// 真正加入購物車(...略...)

// 加入購物車之後foreach ($this->_pluginList => $plugin) {

$plugin->afterAddItem($itemKey);

}

}

}

在這裡插入加入購物車後的程式

Page 113: 購物車程式架構簡介

// Plugin 類別長什麼樣子??

Page 114: 購物車程式架構簡介

// Plugin 類別長什麼樣子??

abstract class Cart_Plugin {

}

首先要有一個抽象類別

Page 115: 購物車程式架構簡介

// Plugin 類別長什麼樣子??

abstract class Cart_Plugin {

protected $_cart = null;

}

為了可直接取得購物車的資訊所以有一個購物車屬性

Page 116: 購物車程式架構簡介

// Plugin 類別長什麼樣子??

abstract class Cart_Plugin {

protected $_cart = null;

public function __construct(Cart $cart) {

$this->_cart = $cart;

}

}

購物車屬性可由建構式的參數帶入

Page 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 方法

(預設為空實作)

Page 118: 購物車程式架構簡介

// 如何加入 Plugin ??

Page 119: 購物車程式架構簡介

// 如何加入 Plugin ??

class Cart {

protected $_pluginList = array();

}

剛剛的購物車類別

Page 120: 購物車程式架構簡介

// 如何加入 Plugin ??

class Cart {

protected $_pluginList = array();

public function registerPlugin($name)

{

}

}

加入註冊 Plugin 的方法

Page 121: 購物車程式架構簡介

// 如何加入 Plugin ??

class Cart {

protected $_pluginList = array();

public function registerPlugin($name)

{

$name = ucfirst($name);

$pluginName = 'Cart_Plugin_' . $name;

}

}

轉換成實際 Plugin 的類別名稱

Page 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 物件使用

Page 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 類別不存在的話,就丟出異常

Page 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();

建立一個新購物車來測試

Page 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

Page 126: 購物車程式架構簡介

// 計算小計與總計金額的 Plugin 類別範例

Page 127: 購物車程式架構簡介

// 計算小計與總計金額的 Plugin 類別範例

class Cart_Plugin_Total

{

}

Page 128: 購物車程式架構簡介

// 計算小計與總計金額的 Plugin 類別範例

class Cart_Plugin_Total extends Cart_Plugin

{

}

繼承抽象的 Cart_Plugin 類別

Page 129: 購物車程式架構簡介

// 計算小計與總計金額的 Plugin 類別範例

class Cart_Plugin_Total extends Cart_Plugin

{

protected $_total = 0;

}

用來記住總金額的屬性

Page 130: 購物車程式架構簡介

// 計算小計與總計金額的 Plugin 類別範例

class Cart_Plugin_Total extends Cart_Plugin

{

protected $_total = 0;

public function beforeRefreshCart(&$iterator)

{

$this->_total = 0;

}

}

更新購物車前先將總金額歸零

Page 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'];

}

}

在更新商品項目後,更新商品小計與總金額

Page 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;

}

} 取得總金額

Page 133: 購物車程式架構簡介

// 如何存取 Plugin 的值??

Page 134: 購物車程式架構簡介

// 如何存取 Plugin 的值??

class Cart {

}

Page 135: 購物車程式架構簡介

// 如何存取 Plugin 的值??

class Cart {

public function __call($name, $args)

{

}

}

使用 __call 魔術方法來間接呼叫 plugin 的方法這樣一來就不用寫過多的 setter / getter

Page 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 方法

Page 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 方法

Page 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

Page 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();

建立測試用的購物車物件

Page 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

Page 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 的值

Page 142: 購物車程式架構簡介

Plugin 模式的優點

Page 143: 購物車程式架構簡介

Plugin 模式的優點

• 不用把過多的功能塞在一個類別上

例如活動、金流、折價券等,可以分開獨立成不同 Plugin

Page 144: 購物車程式架構簡介

Plugin 模式的優點

• 不用把過多的功能塞在一個類別上

• 新增移除功能非常容易

像是系統要需要新增金流,只要加入新的 Plugin 與畫面即可

Page 145: 購物車程式架構簡介

Plugin 模式的優點

• 不用把過多的功能塞在一個類別上

• 新增移除功能非常容易

• Plugin 的職責明確

每個 Plugin 只做自己該做的,不會影響到其他程式

Page 146: 購物車程式架構簡介

Plugin 模式的缺點

Page 147: 購物車程式架構簡介

Plugin 模式的缺點

• 產生過多的類別

因為每個功能都會需要一個類別

Page 148: 購物車程式架構簡介

Plugin 模式的缺點

• 產生過多的類別

• 可能會產生重複的資源

相同的資源,卻分別在不同的 Plugin 產生

Page 149: 購物車程式架構簡介

Plugin 模式的缺點

• 產生過多的類別

• 可能會產生重複的資源

• 一般開發者沒有經過說明不易理解

再加上如果對物件導向不夠熟悉,就更難對這樣的架構進行維護

Page 150: 購物車程式架構簡介

Plugin 模式要注意的地方

Page 151: 購物車程式架構簡介

Plugin 模式要注意的地方

• 流程細膩度

切得越細, Plugin 可 Hook 的地方越多,但也會造成開發上的困擾

Page 152: 購物車程式架構簡介

Plugin 模式要注意的地方

• 流程細膩度

• Plugin 安插的優先順序

例如免運費是需要先知道總金額,所以總金額 Plugin 就要放在前面

Page 153: 購物車程式架構簡介

Plugin 模式要注意的地方

• 流程細膩度

• Plugin 安插的優先順序

• Plugin 之間的相依性

例如免運費是需要知道總金額,所以免運費 Plugin 相依於總金額 Plugin

Page 154: 購物車程式架構簡介

Plugin 模式要注意的地方

• 流程細膩度

• Plugin 安插的優先順序

• Plugin 之間的相依性

• Plugin 存取外部資源的部份也要分離

可以用 Dao 來分離資料庫或是外部金流程式,讓測試容易進行

Page 155: 購物車程式架構簡介

測試購物車

Page 156: 購物車程式架構簡介

測試購物車

• 用 PHPUnit 來做測試框架

PHPUnit 是目前較常見的測試框架

Page 157: 購物車程式架構簡介

測試購物車

• 用 PHPUnit 來做測試框架

• 只測試重要的類別

因為測試購物車的目的是在於驗證複雜的計算

Page 158: 購物車程式架構簡介

測試購物車

• 用 PHPUnit 來做測試框架

• 只測試重要的類別

• 使用假的 Stub 類別來做測試

因為 PHPUnit 提供的 Mock 機制操作上太繁瑣

Page 159: 購物車程式架構簡介

整合到 MVC

Page 160: 購物車程式架構簡介

整合到 MVC

• 購物車本身與 MVC 架構無關

任何 Framework 甚至純 PHP 都可以整合

Page 161: 購物車程式架構簡介

整合到 MVC

• 購物車本身與 MVC 架構無關

• Cart 即 Model

要注意資料庫存取的方式

Page 162: 購物車程式架構簡介

整合到 MVC

• 購物車本身與 MVC 架構無關

• Cart 即 Model

• Controller 呼叫 Cart 方法

Controller 也只會看到 Cart

Page 163: 購物車程式架構簡介

整合到 MVC

• 購物車本身與 MVC 架構無關

• Cart 即 Model

• Controller 呼叫 Cart 方法

• View 取得 Cart 資料

所以畫面也可以客制化

Page 164: 購物車程式架構簡介

問題與討論

Page 165: 購物車程式架構簡介

問題與討論

• 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要?

Page 166: 購物車程式架構簡介

問題與討論

• 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要?

• Plugin 模式的應用

Page 167: 購物車程式架構簡介

問題與討論

• 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要?

• Plugin 模式的應用▫ Zend Framework Dispatcher

Page 168: 購物車程式架構簡介

問題與討論

• 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要?

• Plugin 模式的應用▫ Zend Framework Dispatcher

▫ 是否可用在 Template 上?

Page 169: 購物車程式架構簡介

問題與討論

• 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要?

• Plugin 模式的應用▫ Zend Framework Dispatcher

▫ 是否可用在 Template 上?

• 現有架構可否加入 Plugin 模式?

Page 170: 購物車程式架構簡介

問題與討論

• 為什麼 Plugin 需要包含 Cart 物件,但 Storage 和 Dao 不需要?

• Plugin 模式的應用▫ Zend Framework Dispatcher

▫ 是否可用在 Template 上?

• 現有架構可否加入 Plugin 模式?

• 實際應用時,效能的考量?

Page 171: 購物車程式架構簡介

謝謝