设计模式:策略模式
目前为止我们已经在这个系列中接触了三个设计模式。我们定义了4种类型的设计模式。在这篇文章中,我将讲解 策略模式,这是属于行为类别的设计模式的。
你可能会有一个疑问:我们什么时候该使用这个模式呢?当我们有不同的方式(算法)来执行同样的操作,而我们希望应用可以根据传入的参数来选择合适的方式去执行。
一个非常简单的例子就是排序。例如,我们有不同的算法来排序数组元素,但是需要根据数组中元素的个数来选择性能最好的算法。
问题
我将拿一个电子商务网站来作为例子。这个网站有多种支付通道,但是这些支付请求不会在前端显示出来,而是会根据用户购物车里面的商品价值来选择合适的支付通道。
一个实际点的例子就是,如果购物车里的商品价值少于$500,应该选择标准的PayPal支付通道,但是如果大于或等于$500,那么应该使用信用卡支付通道(假设已经收集了用户的信用卡信息)。
如果没有实现一个合适的策略,我们的代码将会像下面这样:
首先,我们有一个主类,包含了使用Paypal和信用卡支付的方法:
// Class to pay using Credit Card
class payByCC {
private $ccNum = '';
private $ccType = '';
private $cvvNum = '';
private $ccExpMonth = '';
private $ccExpYear = '';
public function pay($amount = 0) {
echo "Paying ". $amount. " using Credit Card";
}
}
// Class to pay using PayPal
class payByPayPal {
private $payPalEmail = '';
public function pay($amount = 0) {
echo "Paying ". $amount. " using PayPal";
}
}
// This code needs to be repeated every place where ever needed.
$amount = 5000;
if($amount >= 500) {
$pay = new payByCC();
$pay->pay($amount);
} else {
$pay = new payByPayPal();
$pay->pay($amount);
}
想象一下,上面中最后的一段代码将会出现在程序的各个地方,如果有一个新的逻辑需要添加或者需要改变旧的逻辑,你需要在每个出现这段代码的地方修补,这是很容易导致bug的。
解决方法
我们将使用策略模式实现同样的需求,这会让我们的代码非常整洁,易懂,可扩展。
接口
首先,我们定义一个接口,让所有不同的支付类实现这个接口:
interface payStrategy {
public function pay($amount);
}
class payByCC implements payStrategy {
private $ccNum = '';
private $ccType = '';
private $cvvNum = '';
private $ccExpMonth = '';
private $ccExpYear = '';
public function pay($amount = 0) {
echo "Paying ". $amount. " using Credit Card";
}
}
class payByPayPal implements payStrategy {
private $payPalEmail = '';
public function pay($amount = 0) {
echo "Paying ". $amount. " using PayPal";
}
}
接下来,我们将创建主类,可以使用我们已经创建的不同的策略。
class shoppingCart {
public $amount = 0;
public function __construct($amount = 0) {
$this->amount = $amount;
}
public function getAmount() {
return $this->amount;
}
public function setAmount($amount = 0) {
$this->amount = $amount;
}
public function payAmount() {
if($this->amount >= 500) {
$payment = new payByCC();
} else {
$payment = new payByPayPal();
}
$payment->pay($this->amount);
}
}
这里你可以看到,我们的条件加载不同支付方法放在了payAmount 方法中。让我们把所有代码组合起来,看看我们如何使用这个:
interface payStrategy {
public function pay($amount);
}
class payByCC implements payStrategy {
private $ccNum = '';
private $ccType = '';
private $cvvNum = '';
private $ccExpMonth = '';
private $ccExpYear = '';
public function pay($amount = 0) {
echo "Paying ". $amount. " using Credit Card";
}
}
class payByPayPal implements payStrategy {
private $payPalEmail = '';
public function pay($amount = 0) {
echo "Paying ". $amount. " using PayPal";
}
}
class shoppingCart {
public $amount = 0;
public function __construct($amount = 0) {
$this->amount = $amount;
}
public function getAmount() {
return $this->amount;
}
public function setAmount($amount = 0) {
$this->amount = $amount;
}
public function payAmount() {
if($this->amount >= 500) {
$payment = new payByCC();
} else {
$payment = new payByPayPal();
}
$payment->pay($this->amount);
}
}
$cart = new shoppingCart(499);
$cart->payAmount();
// Output
Paying 499 using PayPal
$cart = new shoppingCart(501);
$cart->payAmount();
//Output
Paying 501 using Credit Card
你可以看到,支付通道的选择对于应用来说是不透明的。根据传入的参数,它可以选择可用的,合适的支付通道。
添加一个新的策略
如果过了不久,用户需要添加一个新的策略(新的支付通道),用不同的逻辑,这种情况将会非常简单。假如我们需要添加一个新的支付通道 moneybooker, 当购物车商品价值多于$500,小于$1000时,使用这种通道。
我们只需要创建一个新的策略类,实现我们定义的接口:
class payByMB implements payStrategy {
private $mbEmail = '';
public function pay($amount = 0) {
echo "Paying ". $amount. " using Money Booker";
}
}
有了新的策略类后,我们需要在主方法 payAmount 中进行相应的修改:
public function payAmount() {
if($this->amount > 500 && $this->amount < 1000) {
$payment = new payByMB();
} else if($this->amount >= 500) {
$payment = new payByCC();
} else {
$payment = new payByPayPal();
}
$payment->pay($this->amount);
}
这样就可以了,只需要修改 payAmount 方法。
总结
当我们有不同的方式去执行同样的任务时(在软件编程语言中就是有不同的算法去执行同样的操作),我们就应该考虑使用策略模式。