用品

根據SOLID原理打開/關閉

軟件實體(類,模塊,功能等)應打開以進行擴展,但應關閉以進行編輯。

設計軟件:模塊,類和功能的方式是,當需要新功能時,我們不應修改現有代碼,而應編寫將由現有代碼使用的新代碼。 這聽起來可能很奇怪,尤其是對於Java,C,C ++或C#之類的語言而言,它們不僅適用於源代碼本身,而且適用於二進製文件。 我們希望以不需要重新分發現有二進製文件,可執行文件或DLL的方式創建新功能。
SOLID上下文中的OCP

 

SRP和OCP互補

我們已經看到了SRP的“單一職責”原則,該原則指出模塊應該只有一個更改的理由。 OCP和SRP原則是互補的。 遵循SRP原則設計的代碼也將遵守OCP原則。 當我們的代碼只有一個更改的原因時,引入一項新功能將為該更改創建第二個原因。 因此,將同時違反SRP和OCP。 同樣,如果我們的代碼僅在其主要功能發生變化時才應更改,而在添加新功能時應保持不變,從而尊重OCP,那麼它也將主要遵循SRP。
這並不意味著SRP總是導致OCP,反之亦然,但是在大多數情況下,如果堅持其中之一,則實現第二個目標非常簡單。

 

違反OCP原則的示例

從純粹的技術角度來看,“開放/封閉原則”非常簡單。 兩個類之間的簡單關係(如下面的類)違反了OCP原則。

User類直接使用Logic類。 如果我們需要以允許我們同時使用當前和新邏輯類的方式來實現第二個Logic類,則需要更改現有的Logic類。 用戶直接依賴於邏輯的實現,我們無法在不影響當前邏輯的情況下提供新邏輯。 當我們談論靜態類型的語言時,User類也很可能需要更改。 如果我們談論的是編譯語言,那麼肯定要對User可執行文件和Logic可執行文件或動態庫都進行重新編譯和交付,最好盡可能避免。

參考前面的方案,我們可以推斷出,任何直接使用另一個類的類都可能導致違反“打開/關閉”原則。 
假設我們要編寫一個類,該類可以通過我們的應用程序提供下載文件進度的“百分比”。 我們將有兩個主要的類,一個Progress和一個File,我想我們想按如下方式使用它們:

 

函數testItCanGetTheProgressOfAFileAsAPercent(){
     $ file = new File();
     $ file-> length = 200;
     $ file-> send = 100;
     $ progress =新進度($文件);
     $ this-> assertEquals(50,$ progress-> getAsPercent());
}

在此代碼中,我們是Progress用戶。 無論實際文件大小如何,我們都希望獲得一個百分比值。 我們使用File作為信息源。 一個文件的長度以字節為單位,一個名為send的字段表示發送給下載程序的數據量。 我們不在乎如何在應用程序中更新這些值。 我們可以假設有一些神奇的邏輯可以為我們做這件事,因此在測試中我們可以顯式設置它們。

 

類文件{
     公共$長度;
     公開的$已發送;
}

 

File類只是一個包含兩個字段的簡單數據對象。 當然,它還應該包含其他信息和行為,例如文件名,路徑,相對路徑,當前目錄,類型,權限等。

 

課程進度{

     私人$文件;

     函數__construct(文件$文件){
          $ this-> file = $ file;
     }

     函數getAsPercent(){
          返回$ this-> file->發送* 100 / $ this-> file->長度;
     }

}

Progress只是一個在其構造函數中接受文件的類。 為了清楚起見,我們在構造函數參數中指定了變量類型。 Progress上有一個有用的方法getAsPercent(),它將從File中獲取發送的值和長度並將其轉換為百分比。 簡單且有效。

該代碼似乎是正確的,但是它違反了“打開/關閉”原則。

但為什麼?

如何?

 

讓我們嘗試更改需求

隨著時間的發展,每個應用程序都將需要新功能。 我們的應用程序的一項新功能可能是允許音樂流式傳輸,而不僅僅是下載文件。 文件的長度以字節為單位,音樂的持續時間以秒為單位。 我們想為監聽器提供進度條,但是我們可以重用上面編寫的類嗎?

不,我們不可以。 我們的進步與檔案息息相關。 儘管它也可以應用於音樂內容,但它只能管理文件信息。 但是要做到這一點,我們必須對其進行修改,我們必須讓Progress知道音樂和文件。 如果我們的設計符合OCP,則無需觸摸文件或進度。 我們可以重用現有的進度並將其應用於音樂。

 

創新通訊
不要錯過有關創新的最重要新聞。 註冊以通過電子郵件接收它們。

可能的解決方案

動態類型語言具有在運行時管理對像類型的優勢。 這使我們可以從Progress構造函數中刪除typehint,並且代碼將繼續工作。

課程進度{

     私人$文件;

     函數__construct($文件){
         $ this-> file = $ file;
     }

    函數getAsPercent(){
         返回$ this-> file->發送* 100 / $ this-> file->長度;
     }

}

現在,我們可以在Progress中啟動任何東西。 無論如何,我的意思是任何東西:

音樂類{

公共$長度;
公開的$已發送;

公共藝術家
公開$專輯;
public $ releaseDate;

函數getAlbumCoverFile(){
返回“圖片/封面/”。 $ this->藝術家。 '/'。 $ this->相簿。 '.png';
}
}

而且像上面的音樂課將完美地工作。 我們可以使用非常類似於File的測試來輕鬆對其進行測試。
函數testItCanGetTheProgressOfAMusicStreamAsAPercent(){
$ music =新音樂();
$ music-> length = 200;
$音樂->發送= 100;

$ progress =新進度($音樂);

$ this-> assertEquals(50,$ progress-> getAsPercent());
}

因此,基本上所有可衡量的內容都可以與Progress類一起使用。 也許我們應該通過更改變量名來用代碼表達它:

課程進度{

私人$ measurableContent;

函數__construct($ measurableContent){
$ this-> measurableContent = $ measurableContent;
}

函數getAsPercent(){
返回$ this-> measurableContent->發送* 100 / $ this-> measurableContent->長度;
}

}

當我們將File指定為類型提示時,我們對類可以處理的內容感到樂觀。 這很明顯,如果還有其他事情發生,他告訴了我們一個大錯誤。

U覆蓋基類方法的類,以使派生類不遵守基類合同。 

我們不想最終嘗試在不符合我們合同的對像上調用方法或訪問字段。 當我們有類型提示時,合同就是由它指定的。 File類的字段和方法。 現在我們什麼都沒有了,我們可以發送任何東西,即使是字符串也可能導致錯誤。

雖然兩種情況的最終結果是相同的,這意味著代碼被破壞,但前者產生了一個很好的消息。 然而,這是非常黑暗的。 沒有辦法知道變量是什麼(在我們的例子中是一個字符串)以及搜索了哪些屬性但沒有找到。 調試和修復問題很困難。 程序員必須打開Progress類,閱讀並理解它。 在這種情況下,當您沒有明確指定類型提示時,合同是 defi由進步的行為完成。 這是一份只有 Progress 知道的默示合同。 在我們的例子中,它是 defi通過訪問 getAsPercent() 方法中的發送和長度這兩個字段來完成。 在現實生活中,隱含的契約可能非常複雜,很難通過在課堂上花幾秒鐘的時間來發現。

僅當以下任何其他技巧都無法輕鬆實現,或者如果它們造成嚴重的體系結構更改而不值得付出努力時,才建議使用此解決方案。

Ercole Palmeri

創新通訊
不要錯過有關創新的最重要新聞。 註冊以通過電子郵件接收它們。

最近的文章

智慧鎖市場:市場研究報告發布

智慧鎖市場一詞是指圍繞生產、分銷和使用的行業和生態系統…

3月27 2024

什麼是設計模式:為什麼要使用它們、分類、優點和缺點

在軟體工程中,設計模式是軟體設計中常見問題的最佳解決方案。我很像…

3月26 2024

工業標識的技術演變

工業標記是一個廣泛的術語,包含多種用於在...表面建立永久標記的技術。

3月25 2024

使用 VBA 編寫的 Excel 巨集範例

以下簡單的 Excel 巨集範例是使用 VBA 編寫的 預計閱讀時間:3 分鐘 範例...

3月25 2024