JavaRush /Java Blog /Random-TW /Java 類別設計 (SOLID) 的五個基本原則
Ve4niY
等級 14

Java 類別設計 (SOLID) 的五個基本原則

在 Random-TW 群組發布
類別是建立應用程式的區塊。就像建築物中的磚塊一樣。寫得不好的課程有一天可能會引起問題。 Java 類別設計 (SOLID) 的五個基本原則 - 1要了解一個類別是否編寫正確,可以檢查“品質標準”。在Java中,這些就是所謂的SOLID原則。我們來談談他們。

Java 的堅實原則

SOLID 是由 OOP 和設計的前五個原則的大寫字母組成的縮寫。這些原則是由羅伯特馬丁 (Robert Martin) 在 2000 年代初發明的,這個縮寫詞後來由邁克爾費瑟斯 (Michael Feathers) 創造。SOLID 原則包括以下:
  1. 單一責任原則。
  2. 開閉原則。
  3. 里氏替換原理。
  4. 介面隔離原則。
  5. 依賴倒置原則。

單一職責原則(SRP)

這原則規定,改變一個類的理由永遠不應該超過一個。 每個物件都有一個職責,完全封裝在一個類別中。所有班級服務都旨在確保這項責任。如果需要的話,這樣的類別總是很容易更改,因為很清楚該類別負責什麼,不負責什麼。也就是說,可以進行更改並且不必擔心後果 - 對其他物件的影響。而且這樣的程式碼更容易測試,因為您可以透過與所有其他功能隔離的測試來覆寫一項功能。想像一個處理訂單的模組。如果訂單格式正確,則會將其保存在資料庫中並發送電子郵件以確認訂單:
public class OrderProcessor {

    public void process(Order order){
        if (order.isValid() && save(order)) {
            sendConfirmationEmail(order);
        }
    }

    private boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }

    private void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Sending a letter to the client
    }
}
這樣的模組可能會因三個原因而改變。首先,訂單處理邏輯可能不同,其次,保存方法(資料庫類型),第三,發送確認信的方法(例如,您需要發送簡訊而不是電子郵件)。單一職責原則意味著這個問題的三個面向實際上是三個不同的職責。這意味著它們必須位於不同的類別或模組中。組合可能在不同時間和出於不同原因發生變化的多個實體被認為是糟糕的設計決策。最好將模組分為三個獨立的模組,每個模組執行一個功能:
public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Sending a letter to the client
    }
}

public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}

開閉原理 (OCP)

這個原則簡潔地描述如下:軟體實體(類別、模組、函數等)必須對擴充開放,但對變更封閉。這意味著應該可以更改類別的外部行為,而無需對類別本身進行物理更改。遵循這個原則,開發類,為了使類別適應特定的應用條件,只需擴展它並重新定義一些功能。因此,系統必須是靈活的,能夠在不改變原始程式碼的情況下在變化的條件下工作。繼續我們的訂單範例,假設我們需要在處理訂單之前和發送確認電子郵件之後執行一些操作。我們不會改變類別本身OrderProcessor,而是擴展它並在不違反 OCP 原則的情況下實現手頭問題的解決方案:
public class OrderProcessorWithPreAndPostProcessing extends OrderProcessor {

    @Override
    public void process(Order order) {
        beforeProcessing();
        super.process(order);
        afterProcessing();
    }

    private void beforeProcessing() {
        // Perform some actions before processing the order
    }

    private void afterProcessing() {
        // Perform some actions after order processing
    }
}

芭芭拉·利斯科夫替換原理 (LSP)

這是前面討論的開放/封閉原則的變體。可以這樣描述:程式中的物件可以被其繼承者替換,而不改變程式的屬性。這意味著透過擴展基類開發的類別必須以不破壞客戶端觀點的功能的方式重寫其方法。也就是說,如果開發人員擴展您的類別並在應用程式中使用它,他不應該更改重寫方法的預期行為。從客戶端的角度來看,子類別必須以不破壞功能的方式重寫基底類別方法。可以使用以下範例詳細檢查這一點。假設我們有一個類別負責訂單驗證並檢查所有訂單商品是否都有庫存。此類有一個isValid傳回truefalse的方法:
public class OrderStockValidator {

    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if (! item.isInStock()) {
                return false;
            }
        }

        return true;
    }
}
我們也假設某些訂單需要不同的驗證:檢查訂單中的所有商品是否都有庫存以及所有商品是否都已包裝。為此,我們OrderStockValidator用以下類別擴展了該類別OrderStockAndPackValidator
public class OrderStockAndPackValidator extends OrderStockValidator {

    @Override
    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if ( !item.isInStock() || !item.isPacked() ){
                throw new IllegalStateException(
                     String.format("Order %d is not valid!", order.getId())
                );
            }
        }

        return true;
    }
}
然而,在這個類別中我們違反了 LSP 原則,因為如果訂單沒有通過驗證,我們的方法不會回傳falseIllegalStateException ,而是會拋出異常。此程式碼的客戶端並不期望這樣:他們期望傳回truefalse。這可能會導致程式出錯。

介面分割原理(ISP)

其特點是以下陳述:不應強迫客戶實現他們不會使用的方法。接口分離的原則是,過於「厚」的接口需要被分成更小、更具體的接口,這樣小接口的客戶端只知道其工作所必需的方法。因此,當更改介面方法時,不使用該方法的客戶端不應更改。讓我們來看一個例子。開發人員 Alex 創建了「報告」介面並添加了兩個方法:generateExcel()generatedPdf()。現在客戶A想要使用這個介面,但他只想使用PDF報告,而不是Excel。他對這個功能滿意嗎?不。他必須實施兩種方法,其中一種基本上是不必要的,並且要感謝軟體設計師亞歷克斯才能存在。客戶端將使用不同的介面或將 Excel 欄位留空。那麼解決方法是什麼呢?它包括將現有介面分為兩個較小的介面。第一個是 PDF 格式的報告,第二個是 Excel 格式的報告。這將使用戶有機會僅使用他所需的功能。

依賴倒置原則(DIP)

Java中的SOLID原則描述如下:系統內的依賴關係是建立在抽象的基礎上的。頂層模組獨立於下層模組。抽像不應該依賴細節。細節必須依賴抽象。軟體的設計需要確保各個模組是自治的,並使用抽象相互連接。Spring 框架是這項原則的典型應用。在 Spring 框架內,所有模組都實作為可協同工作的單獨元件。它們是如此獨立,以至於可以在 Spring 框架之外的其他軟體模組中輕鬆使用。這是透過封閉和開放原則的依賴來實現的。所有模組僅提供對可在另一個模組中使用的抽象的存取。讓我們試著用一個例子來證明這一點。談到單一責任原則,我們考慮了一些OrderProcessor。我們再看一下這個類別的程式碼:
public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}
在此範例中,我們的OrderProcessor依賴於兩個特定的類別MySQLOrderRepositoryConfirmationEmailSender。我們也提供了這些類別的程式碼:
public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Sending a letter to the client
    }
}
這些類別遠遠不能稱為抽象。從 DIP 原則的角度來看,從創建一些抽象開始,讓我們將來能夠使用它們,而不是具體的實現,會更正確。讓我們創建兩個介面MailSenderOrderRepository,它將成為我們的抽象:
public interface MailSender {
    void sendConfirmationEmail(Order order);
}

public interface OrderRepository {
    boolean save(Order order);
}
現在讓我們在已經為此做好準備的類別中實作這些介面:
public class ConfirmationEmailSender implements MailSender {

    @Override
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Sending a letter to the client
    }

}

public class MySQLOrderRepository implements OrderRepository {

    @Override
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }
}
我們已經完成了準備工作,以便我們的類別OrderProcessor不依賴具體細節,而是依賴抽象。讓我們透過在類別建構函數中引入依賴項來對其進行更改:
public class OrderProcessor {

    private MailSender mailSender;
    private OrderRepository repository;

    public OrderProcessor(MailSender mailSender, OrderRepository repository) {
        this.mailSender = mailSender;
        this.repository = repository;
    }

    public void process(Order order){
        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }
}
我們的類別現在依賴抽象而不是具體的實作。您可以透過在建立實例時注入所需的依賴項來輕鬆變更其行為OrderProcessor。我們研究了 Java 中的 SOLID 設計原則。更多有關 OOP 的一般資訊以及這種程式語言的基礎知識 - 並不乏味,需要數百小時的練習 - 在 JavaRush 課程中。是時候解決一些問題了:) Java 類別設計 (SOLID) 的五個基本原則 - 2
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION