CodeGym /Java Blog /Toto sisi /SOLID:Java 類設計的五個基本原則
John Squirrels
等級 41
San Francisco

SOLID:Java 類設計的五個基本原則

在 Toto sisi 群組發布
類是應用程序的構建塊。就像建築物中的磚塊一樣。寫得不好的類最終會導致問題。 SOLID:Java 類設計的五個基本原則 - 1要了解一個類是否編寫得當,您可以檢查它如何達到“質量標準”。在 Java 中,這些就是所謂的 SOLID 原則,我們將討論它們。

Java 中的 SOLID 原則

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 in the database

        return true;
    }

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

        // Send an email to the customer
    }
}
這個模塊可能會因為三個原因而改變。首先,處理訂單的邏輯可能會改變。其次,訂單的保存方式(數據庫類型)可能會發生變化。第三,發送確認的方式可能會改變(例如,假設我們需要發送短信而不是電子郵件)。單一職責原則意味著這個問題的三個方面實際上是三個不同的職責。這意味著它們應該在不同的類或模塊中。將可能在不同時間和出於不同原因更改的多個實體組合在一起被認為是糟糕的設計決策。最好將一個模塊拆分為三個獨立的模塊,每個模塊執行一個功能:

public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }
}

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

        // Send an email to the customer
    }
}

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類本身,我們將擴展它以在不違反開閉原則的情況下實現我們的目標:

public class OrderProcessorWithPreAndPostProcessing extends OrderProcessor {

    @Override
    public void process(Order order) {
        beforeProcessing();
        super.process(order);
        afterProcessing();
    }
    
    private void beforeProcessing() {
        // Take some action before processing the order
    }
    
    private void afterProcessing() {
        // Take some action after processing the order
    }
}

里氏替換原則 (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;
    }
}
但是這裡我們違反了 Liskov 替換原則,因為如果訂單驗證失敗,我們的方法 不會返回falseIllegalStateException ,而是拋出一個. 使用此代碼的客戶並不期望這樣:他們期望返回值為truefalse。這可能導致運行時錯誤。

接口隔離原則(ISP)

這個原則的特點是下面的陳述:客戶不應該被強制實施他們不會使用的方法。接口隔離原則意味著必須將太“厚”的接口分成更小、更具體的接口,以便使用小接口的客戶只知道他們工作所需的方法。因此,當接口方法改變時,任何不使用該方法的客戶端都不應該改變。考慮這個例子:開發人員亞歷克斯創建了一個“報告”界面並添加了兩個方法:generateExcel()generatedPdf(). 現在有個客戶想用這個接口,但是只打算用PDF格式的報表,不打算用Excel。這個功能會讓這個客戶滿意嗎?不。客戶將必須實施兩種方法,其中一種基本上不需要,並且僅由於設計該軟件的亞歷克斯而存在。客戶端將使用不同的界面或不對 Excel 報告的方法進行任何操作。那麼解決方案是什麼?就是將現有的界面拆分成兩個較小的界面。一個用於 PDF 報告,另一個用於 Excel 報告。這讓客戶只使用他們需要的功能。

依賴倒置原則(DIP)

在Java中,這個SOLID原則是這樣描述的:系統內的依賴關係是基於抽象構建的. 高層模塊不依賴於底層模塊。抽像不應依賴於細節。細節應該依賴於抽象。軟件需要設計成各個模塊是獨立的,並通過抽象相互連接。這一原則的一個經典應用是 Spring 框架。在 Spring Framework 中,所有模塊都實現為可以協同工作的獨立組件。它們非常自治,可以在除 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 in the database

        return true;
    }
}

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

        // Send an email to the customer
    }
}
這些類遠非我們所說的抽象。從依賴倒置原則的角度來看,最好從創建一些我們將來可以使用的抽像開始,而不是具體的實現。讓我們創建兩個接口: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();

        // Send an email to the customer
    }

}

public class MySQLOrderRepository implements OrderRepository {

    @Override
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in 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 設計原則。在 CodeGym 課程中,您將學到更多關於 OOP 的一般知識和 Java 編程的基礎知識——沒有什麼枯燥乏味和數百小時的練習。是時候解決一些任務了:)
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION