CodeGym/Blog Java/Ngẫu nhiên/SOLID: Năm nguyên tắc cơ bản của thiết kế lớp trong Java

SOLID: Năm nguyên tắc cơ bản của thiết kế lớp trong Java

Xuất bản trong nhóm
Các lớp là các khối xây dựng của các ứng dụng. Cũng giống như những viên gạch trong một tòa nhà. Các lớp viết kém cuối cùng có thể gây ra vấn đề. SOLID: Năm nguyên tắc cơ bản của thiết kế lớp trong Java - 1Để hiểu liệu một lớp có được viết đúng cách hay không, bạn có thể kiểm tra xem lớp đó đạt "tiêu chuẩn chất lượng" như thế nào. Trong Java, đây được gọi là các nguyên tắc RẮN, và chúng ta sẽ nói về chúng.

Nguyên tắc SOLID trong Java

SOLID là từ viết tắt được hình thành từ các chữ in hoa của năm nguyên tắc đầu tiên của OOP và thiết kế lớp học. Các nguyên tắc được Robert Martin thể hiện vào đầu những năm 2000, và sau đó cách viết tắt được giới thiệu sau đó bởi Michael Feathers. Dưới đây là các nguyên tắc RẮN:
  1. Nguyên tắc trách nhiệm duy nhất
  2. Nguyên tắc đóng mở
  3. Nguyên tắc thay thế Liskov
  4. Nguyên tắc phân tách giao diện
  5. Nguyên tắc đảo ngược phụ thuộc

Nguyên tắc trách nhiệm duy nhất (SRP)

Nguyên tắc này nói rằng không bao giờ có nhiều hơn một lý do để thay đổi một lớp học. Mỗi đối tượng có một trách nhiệm, được đóng gói đầy đủ trong lớp. Tất cả các dịch vụ của một lớp đều nhằm hỗ trợ trách nhiệm này. Các lớp như vậy sẽ luôn dễ dàng sửa đổi nếu cần thiết, bởi vì rõ ràng lớp đó là gì và không chịu trách nhiệm về điều gì. Nói cách khác, chúng ta sẽ có thể thay đổi và không sợ hậu quả, tức là tác động đến các đối tượng khác. Ngoài ra, mã như vậy dễ kiểm tra hơn nhiều, bởi vì các bài kiểm tra của bạn bao gồm một phần chức năng tách biệt với tất cả các phần khác. Hãy tưởng tượng một mô-đun xử lý các đơn đặt hàng. Nếu một đơn đặt hàng được tạo chính xác, mô-đun này sẽ lưu nó vào cơ sở dữ liệu và gửi email để xác nhận đơn đặt hàng:
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
    }
}
Mô-đun này có thể thay đổi vì ba lý do. Đầu tiên, logic để xử lý các đơn đặt hàng có thể thay đổi. Thứ hai, cách lưu đơn đặt hàng (loại cơ sở dữ liệu) có thể thay đổi. Thứ ba, cách gửi xác nhận có thể thay đổi (ví dụ: giả sử chúng tôi cần gửi tin nhắn văn bản thay vì email). Nguyên tắc trách nhiệm duy nhất ngụ ý rằng ba khía cạnh của vấn đề này thực sự là ba trách nhiệm khác nhau. Điều đó có nghĩa là chúng nên ở trong các lớp hoặc mô-đun khác nhau. Kết hợp một số thực thể có thể thay đổi vào những thời điểm khác nhau và vì những lý do khác nhau được coi là một quyết định thiết kế tồi. Sẽ tốt hơn nhiều nếu chia một mô-đun thành ba mô-đun riêng biệt, mỗi mô-đun thực hiện một chức năng duy nhất:
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);
        }
    }

}

Nguyên tắc Đóng Mở (OCP)

Nguyên tắc này được mô tả như sau: các thực thể phần mềm (lớp, mô-đun, chức năng, v.v.) nên mở để mở rộng, nhưng đóng để sửa đổi . Điều này có nghĩa là có thể thay đổi hành vi bên ngoài của một lớp mà không cần thay đổi mã hiện có của lớp. Theo nguyên tắc này, các lớp được thiết kế sao cho việc điều chỉnh một lớp cho phù hợp với các điều kiện cụ thể chỉ cần mở rộng nó và ghi đè một số chức năng. Điều này có nghĩa là hệ thống phải linh hoạt, có thể làm việc trong các điều kiện thay đổi mà không cần thay đổi mã nguồn. Tiếp tục ví dụ của chúng tôi liên quan đến xử lý đơn hàng, giả sử chúng tôi cần thực hiện một số hành động trước khi đơn hàng được xử lý cũng như sau khi email xác nhận được gửi. Thay vì thay đổiOrderProcessorlớp, chúng tôi sẽ mở rộng nó để đạt được mục tiêu của mình mà không vi phạm Nguyên tắc Đóng Mở:
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
    }
}

Nguyên tắc thay thế Liskov (LSP)

Đây là một biến thể của nguyên tắc đóng mở mà chúng tôi đã đề cập trước đó. Nó có thể được định nghĩa như sau: các đối tượng có thể được thay thế bởi các đối tượng của lớp con mà không làm thay đổi thuộc tính của chương trình. Điều này có nghĩa là một lớp được tạo bằng cách mở rộng một lớp cơ sở phải ghi đè các phương thức của nó để chức năng không bị ảnh hưởng theo quan điểm của máy khách. Nghĩa là, nếu một nhà phát triển mở rộng lớp của bạn và sử dụng nó trong một ứng dụng, họ sẽ không thay đổi hành vi dự kiến ​​của bất kỳ phương thức nào bị ghi đè. Các lớp con phải ghi đè các phương thức của lớp cơ sở để chức năng không bị hỏng theo quan điểm của máy khách. Chúng ta có thể khám phá điều này một cách chi tiết trong ví dụ sau. Giả sử chúng ta có một lớp chịu trách nhiệm xác nhận đơn đặt hàng và kiểm tra xem tất cả hàng hóa trong đơn đặt hàng có còn hàng hay không.isValid()phương thức trả về true hoặc false :
public class OrderStockValidator {

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

        return true;
    }
}
Cũng giả sử rằng một số đơn hàng cần được xác thực khác với những đơn hàng khác, ví dụ: đối với một số đơn hàng, chúng tôi cần kiểm tra xem tất cả hàng hóa trong đơn hàng có còn trong kho hay không và tất cả hàng hóa đã được đóng gói chưa. Để làm điều này, chúng tôi mở rộng OrderStockValidatorlớp bằng cách tạo OrderStockAndPackValidatorlớp:
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;
    }
}
Nhưng ở đây chúng ta đã vi phạm nguyên tắc thay thế Liskov, bởi vì thay vì trả về false nếu lệnh không xác thực, phương thức của chúng ta sẽ đưa ra một IllegalStateException. Khách hàng sử dụng mã này không mong đợi điều này: họ mong đợi giá trị trả về là true hoặc false . Điều này có thể dẫn đến lỗi thời gian chạy.

Nguyên tắc phân tách giao diện (ISP)

Đây là nguyên tắc được đặc trưng bởi tuyên bố sau: khách hàng không nên bị buộc phải thực hiện các phương pháp mà họ sẽ không sử dụng . Nguyên tắc phân tách giao diện có nghĩa là các giao diện quá "dày" phải được chia thành các giao diện nhỏ hơn, cụ thể hơn để khách hàng sử dụng giao diện nhỏ chỉ biết về các phương thức họ cần cho công việc của họ. Do đó, khi một phương thức giao diện thay đổi, bất kỳ máy khách nào không sử dụng phương thức đó sẽ không thay đổi. Xem xét ví dụ sau: Alex, một nhà phát triển, đã tạo giao diện "báo cáo" và thêm hai phương thức: generateExcel()generatedPdf(). Giờ đây, một khách hàng muốn sử dụng giao diện này, nhưng chỉ có ý định sử dụng các báo cáo ở định dạng PDF, không phải ở định dạng Excel. Chức năng này có làm hài lòng khách hàng này không? Không. Khách hàng sẽ phải thực hiện hai phương pháp, một trong số đó phần lớn không cần thiết và chỉ tồn tại nhờ Alex, người đã thiết kế phần mềm. Máy khách sẽ sử dụng một giao diện khác hoặc không làm gì với phương pháp cho các báo cáo Excel. Vậy giải pháp là gì? Đó là chia giao diện hiện có thành hai giao diện nhỏ hơn. Một cho báo cáo PDF, một cho báo cáo Excel. Điều này cho phép khách hàng chỉ sử dụng chức năng họ cần.

Nguyên tắc đảo ngược phụ thuộc (DIP)

Trong Java, nguyên tắc RẮN này được mô tả như sau: các phụ thuộc trong hệ thống được xây dựng dựa trên sự trừu tượng hóa. Các mô-đun cấp cao hơn không phụ thuộc vào các mô-đun cấp thấp hơn. Trừu tượng không nên phụ thuộc vào chi tiết. Chi tiết nên phụ thuộc vào trừu tượng. Phần mềm cần được thiết kế sao cho các mô-đun khác nhau độc lập và được kết nối với nhau thông qua trừu tượng hóa. Một ứng dụng cổ điển của nguyên tắc này là Spring Framework. Trong Spring Framework, tất cả các mô-đun được triển khai dưới dạng các thành phần riêng biệt có thể hoạt động cùng nhau. Chúng tự chủ đến mức chúng có thể được sử dụng dễ dàng trong các mô-đun chương trình khác ngoài Spring Framework. Điều này đạt được nhờ sự phụ thuộc của các nguyên tắc đóng và mở. Tất cả các mô-đun chỉ cung cấp quyền truy cập vào phần trừu tượng, có thể được sử dụng trong một mô-đun khác. Hãy thử minh họa điều này bằng một ví dụ. Nói về nguyên tắc trách nhiệm duy nhất, chúng tôi đã xem xétOrderProcessorlớp học. Chúng ta hãy xem mã của lớp này:
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);
        }
    }

}
Trong ví dụ này, OrderProcessorlớp của chúng ta phụ thuộc vào hai lớp cụ thể: MySQLOrderRepositoryConfirmationEmailSender. Chúng tôi cũng sẽ trình bày mã của các lớp này:
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
    }
}
Các lớp này khác xa với những gì chúng ta gọi là trừu tượng. Và từ quan điểm của nguyên tắc đảo ngược phụ thuộc, sẽ tốt hơn nếu bắt đầu bằng cách tạo một số khái niệm trừu tượng mà chúng ta có thể làm việc trong tương lai, thay vì triển khai cụ thể. Hãy tạo hai giao diện: MailSenderOrderRepository). Đây sẽ là những trừu tượng của chúng tôi:
public interface MailSender {
    void sendConfirmationEmail(Order order);
}

public interface OrderRepository {
    boolean save(Order order);
}
Bây giờ chúng tôi triển khai các giao diện này trong các lớp đã được chuẩn bị cho việc này:
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;
    }
}
Chúng tôi đã làm công việc chuẩn bị để OrderProcessorlớp học của chúng tôi không phụ thuộc vào các chi tiết cụ thể mà phụ thuộc vào những điều trừu tượng. Chúng tôi sẽ thay đổi nó bằng cách thêm các phụ thuộc của chúng tôi vào hàm tạo của lớp:
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);
        }
    }
}
Bây giờ lớp của chúng ta phụ thuộc vào sự trừu tượng hóa, không phải sự triển khai cụ thể. Chúng ta có thể dễ dàng thay đổi hành vi của nó bằng cách thêm phụ thuộc mong muốn vào thời điểm một OrderProcessorđối tượng được tạo. Chúng tôi đã kiểm tra các nguyên tắc thiết kế SOLID trong Java. Bạn sẽ tìm hiểu thêm về OOP nói chung và kiến ​​thức cơ bản về lập trình Java — không có gì nhàm chán và hàng trăm giờ thực hành — trong khóa học CodeGym. Thời gian để giải quyết một vài nhiệm vụ :)
Bình luận
  • Phổ biến
  • Mới
Bạn phải đăng nhập để đăng nhận xet
Trang này chưa có bất kỳ bình luận nào