S.O.I.L.D 之開放封閉原則

心智極客發表於2019-04-26

說明

實體(類、方法等)應當對擴充套件開放,對修改封閉

進一步解釋:

  • 對擴充套件開放,是指程式碼應當很容易新增新功能。
  • 對修改封閉,是指我們在新增新功能的時候儘量不會涉及到原有程式碼的改動。

反面示例

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);
    }
}

相關文章