說明
實體(類、方法等)應當對擴充套件開放,對修改封閉
進一步解釋:
- 對擴充套件開放,是指程式碼應當很容易新增新功能。
- 對修改封閉,是指我們在新增新功能的時候儘量不會涉及到原有程式碼的改動。
反面示例
類 Square
代表「方形」
class Square {
public $width;
public $height;
public function __construct($width, $height)
{
$this->width = $width
$this->height = $height;
}
}
類 AreaCalculator
用於計算圖形的總面積
class AreaCalculator {
public function calculate($shapes)
{
foreach($shapes as $shape)
{
$area[] = $shape->width * $shape->height;
}
return array_sum($area);
}
}
測試:計算幾個方形的總面積
$area = new AreaCalculator;
$area->calculate([
new Square(10,20),
new Square(20,30)
]);
現在我們要新增新的功能。例如,我們要增加一個圓形
class Circle {
public $radius;
public function __construct($radius)
{
$this->radius = $radius;
}
這時候,就必須修改 AreaCalculator
來實現面積的計算
class AreaCalculator {
public function calculate($shape)
{
foreach($shapes as $shape)
{
if (is_a($shape, 'Square'))
{
$area[] = $shape->width * $shape->height;
}
elseif (is_a($shape, 'Circle'))
{
$area[] = $shape->radius * $shape->radius * pi();
}
}
return array_sum($area);
}
}
AreaCalculator
類計算總面積的時候就需要增加對圓形這個圖案的判斷,原有程式碼就必須做出變動。
對照「開放封閉」原則可以發現, AreaCalculator
類對修改是開放的,使得原有程式碼的改動越來越多。例如,後續可能進一步新增三角形等類的計算,導致 AreaCalculator
類的不穩定因素越來越多,變得難以維護,我們稱之為程式碼腐爛。
改進
首先,我們要分析類中可擴充套件的行為是什麼,在本例中,可擴充套件的行為是「面積計算」。接下來,我們需要將可擴充套件的行為分離出來,令其隱藏在介面的背後。這樣的話,類就不需要依賴於可擴充套件的行為,即 AreaCalculator
類不依賴於具體的「面積計算」。
將「面積計算」這一變化的行為隱藏於介面的背後
interface Shape {
public function area();
}
新增不同的子類(三角形、圓形、方形)的時候,讓子類去實現對應的介面。
class Square implements Shape {
public $width;
public function __construct($width)
{
$this->width = $width;
}
public function area()
{
return $this->width * $this->width;
}
}
class Circle implements Square {
public $radios;
public function __construct($radios)
{
$this->radios = $radios;
}
public function area()
{
return $this->radios * $this->radios * pi();
}
}
AreaCalculator
不再依賴於具體的行為,不需要去考慮不同形狀的面積如何計算,只只需依賴於抽象的介面
class AreaCalculator
{
public function calculate($shapes)
{
foreach($shapes as $shape)
{
$area[] = $shape->area();
}
return array_sum($area);
}
}
應用
開放封閉原則的一個典型應用就是「結賬」功能。對於結賬來說,賬單是固定的,但是支付方式卻是變化的(現金、信用卡、微信、支付寶)。
首先,將變化的「支付方式」抽象出來,隱藏於介面背後
interface PaymentMethodInterface
{
public function acceptPayment($receipt);
}
不同的支付方式去實現該介面,例如現金支付
class CashPaymentMethod implements PaymentMethodInterface
{
public function acceptPayment($receipt)
{
// 接受現金支付
}
}
結賬類不需要依賴具體的支付方式,只需要依賴抽象的支付介面即可
class Checkout
{
public function begin(Receipt $receipt, PaymentMethodInterface $paymentMethod)
{
$paymentMethod->acceptPayment($receipt);
}
}