用 PHP 解釋 SOLID 原則裡的 O

程式技術
sharkHead

此為 SOLID 原則介紹的系列文章之一,所有文章的連結如下。

SOLID 原則中的 O

本篇文章要來介紹開放封閉原則 (Open–closed principle,OCP),也是 SOLID 原則中的 O。

開放?封閉?這個原則從名字上來看好像無法猜到其含義,來看看原文怎麼說。

Entities should be open for extension, but closed for modification.
實體對於擴展應該是開放的,但對修改是封閉的。

這裡的實體 (Entities) 可以指的是類別 (Class)、函式 (Function)、方法 (Method),這句話是什麼意思?小弟在 Laracast 中聽到講師是這麼解釋的。

Change behavior of entities without modifying source code.
改變實體的行為,但不修改其程式碼。

What!?

這要怎麼做到?別急,讓我們看看例子吧。

假設我們是某間新創電商的後端工程師,某天接到一個需求,需要開發電商平台的支付功能,這個支付功能只需要支援信用卡就好,於是我們開始著手開發,並在測試通過之後,將信用卡支付功能上線至正式台。

<?php

class Payment
{
    public function pay(Receipt $receipt)
    {
        $this->payByCreditCard();
    }

    // 用信用卡付錢
    public function payByCreditCard()
    {
        // 實作業務邏輯
        ...
    }
}

過了一陣子,電商生意漸漸起色,只支援信用卡支付,顯然無法滿足日漸增多的消費者,在聽到消費者的反應之後,公司希望我們可以幫電商加入超商付款的功能,於是我們修改原本的程式碼。

<?php

class Payment
{
    public function pay(Receipt $receipt)
    {
        // $receipt 多加一個 type 屬性作判斷 (這個方法其實不太好)
        if ($receipt->type === 'cash') {
            $this->payByCash($receipt);
        } else {
            $this->payByCreditCard($receipt);
        }
    }

    // 信用卡支付
    public function payByCreditCard($receipt)
    {
        // ...
    }

    // 超商付款
    public function payByCash($receipt)
    {
        // ...
    }
}

又過了一陣子,電商越做越大,消費者希望支付方式可以更加多元化,於是公司決定再加入 PayPal 與 ATM 付款等新的支付方式,所以辛苦的我們再一次修改程式碼,但是改到一半,我們終於注意到一件事情…

「萬一哪天又要加入虛擬貨幣支付或是其他支付方式,我是不是還要再改一次?」

我們在加入功能的同時,不停的去修改原來的程式碼,這顯然違反了開放封閉原則。
但要怎麼做,才能在不修改原本程式碼的前提下,新增新的支付方式?

Clean Code 的作者羅伯特·C·馬丁 (Uncle Bob) 提出一種方式。

Separate extensible behavior behind an interface and flip the dependencies.
將可擴展的行為用介面包裝,並翻轉依賴關係。

順著這個思路,我們可以將程式碼修改成。

<?php

// 新增一個支付的介面,想要實現這個介面,就必須實作付錢的方法 acceptPayment()
interface PaymentMethodInterface
{
    public function acceptPayment($receipt);
}


// 信用卡支付
class payByCreditCard implements PaymentMethodInterface
{
    public function acceptPayment($receipt)
    {
        // ...
    }
}

// 現金支付
class payByCash implements PaymentMethodInterface
{
    public function acceptPayment($receipt)
    {
        // ...
    }
}

class Payment
{
    // 只要支付方式符合 PaymentMethodInterface 介面,方法 pay 不需要知道支付是使用哪種方式
    public function pay(Receipt $receipt, PaymentMethodInterface $payment)
    {
        $payment->acceptPayment($receipt);
    }
}

這樣的方式,假如日後真的要新增虛擬貨幣的支付功能,我們只需要再加上一段程式碼。

// 虛擬貨幣支付
class payByBitCoin implements PaymentMethodInterface
{
    public function acceptPayment($receipt)
    {
        //
    }
}

不需要動到舊有的程式碼,就能幫程式碼加上新的功能,這就是開放封閉原則的概念。

sharkHead
written by
sharkHead

後端打工仔,在下班後喜歡研究各種不同的技術。稍微擅長 PHP,並偶爾涉獵前端開發。個性就像動態語言般隨興,但渴望做事能像囉嗦的靜態語言那樣嚴謹。

0 則留言