JavaRush /Blog Java /Random-VI /Năm nguyên tắc cơ bản của thiết kế lớp (SOLID) trong Java...
Ve4niY
Mức độ

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

Xuất bản trong nhóm
Các lớp là các khối mà từ đó một ứng dụng được xây dựng. Giống như những viên gạch trong một tòa nhà. Các lớp học viết kém có thể gây ra vấn đề vào một ngày nào đó. Năm nguyên tắc cơ bản của thiết kế lớp (SOLID) trong Java - 1Để hiểu liệu một lớp có được viết chính xác hay không, bạn có thể kiểm tra “tiêu chuẩn chất lượng”. Trong Java, đây được gọi là nguyên tắc RẮN. Hãy nói về họ.

Nguyên tắc RẮN 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ế. Các nguyên tắc này được Robert Martin phát minh vào đầu những năm 2000 và từ viết tắt sau đó được đặt ra bởi Michael Feathers. Dưới đây là những gì nguyên tắc SOLID bao gồm:
  1. Nguyên tắc trách nhiệm duy nhất.
  2. Nguyên tắc đóng mở.
  3. Nguyên lý thay thế của Liskov.
  4. Nguyên tắc phân chia 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êu rõ rằng không bao giờ có nhiều hơn một lý do để thay đổi một lớp. Mỗi đối tượng có một trách nhiệm, được gói gọn hoàn toàn trong một lớp. Tất cả các dịch vụ đẳng cấp đều nhằm mục đích đảm bảo trách nhiệm này. Những lớp như vậy sẽ luôn dễ dàng thay đổi nếu cần thiết, bởi vì nó rõ ràng lớp nào chịu trách nhiệm và cái gì không. Tức là có thể thực hiện những thay đổi mà không sợ hậu quả - ảnh hưởng đến các đối tượng khác. Và mã như vậy dễ kiểm tra hơn nhiều vì bạn bao gồm một chức năng bằng các thử nghiệm tách biệt với tất cả các chức năng khác. Hãy tưởng tượng một mô-đun xử lý các đơn đặt hàng. Nếu đơn hàng được hình thành chính xác, nó sẽ lưu nó vào cơ sở dữ liệu và gửi email để xác nhận đơn 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 to the database

        return true;
    }

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

        // Sending a letter to the client
    }
}
Một mô-đun như vậy có thể thay đổi vì ba lý do. Thứ nhất, logic xử lý đơn hàng có thể khác, thứ hai, phương thức lưu nó (loại cơ sở dữ liệu), thứ ba, phương thức gửi thư xác nhận (ví dụ: thay vì email bạn cần gửi SMS). 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 chất là ba trách nhiệm khác nhau. Điều này có nghĩa là chúng phải ở các lớp hoặc mô-đun khác nhau. Việc kết hợp nhiều 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ô-đun thành ba mô-đun riêng biệt, mỗi mô-đun sẽ 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 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);
        }
    }

}

Nguyên tắc đóng/mở (OCP)

Nguyên tắc này được mô tả ngắn gọn như sau: các thực thể phần mềm (lớp, mô-đun, chức năng, v.v.) phải mở để mở rộng nhưng đóng để thay đổ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 thực hiện các thay đổi vật lý đối với chính lớp đó. Theo nguyên tắc này, các lớp được phát triển để điều chỉnh lớp theo các điều kiện ứng dụng cụ thể, chỉ cần mở rộng và xác định lại một số chức năng là đủ. Vì vậy, hệ thống phải linh hoạt, có thể làm việc trong các điều kiện khác nhau mà không cần thay đổi mã nguồn. Tiếp tục với ví dụ về đơn hàng của chúng ta, giả sử chúng ta cần thực hiện một số hành động trước khi đơn hàng được xử lý và sau khi email xác nhận được gửi. Thay vì tự thay đổi lớp OrderProcessor, chúng tôi sẽ mở rộng nó và đạt được giải pháp cho vấn đề hiện tại mà không vi phạm nguyên tắc 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
    }
}

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

Đây là một biến thể của nguyên tắc đóng/mở đã thảo luận trước đó. Nó có thể được mô tả như sau: các đối tượng trong một chương trình có thể được thay thế bởi các đối tượng kế thừa của chúng mà không làm thay đổi các thuộc tính của chương trình. Điều này có nghĩa là một lớp được phát triển 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ó theo cách không phá vỡ chức năng theo quan điểm của khách hàng. 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, thì anh ta không nên thay đổi hành vi mong đợi của các phương thức bị ghi đè. Các lớp con phải ghi đè các phương thức của lớp cơ sở theo cách không phá vỡ chức năng theo quan điểm của khách hàng. Điều này có thể được kiểm tra chi tiết bằng cách sử dụng ví dụ sau. Giả sử chúng ta có một lớp chịu trách nhiệm xác thực đơn hàng và kiểm tra xem tất cả các mặt hàng trong đơn hàng có còn trong kho hay không. Lớp này có một phương thức isValidtrả 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 theo cách khác: 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 lớp OrderStockValidatorvới lớp 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;
    }
}
Tuy nhiên, trong lớp này, chúng ta đã vi phạm nguyên tắc LSP, vì thay vì trả về false nếu đơn hàng không vượt qua quá trình xác thực, phương thức của chúng ta sẽ đưa ra một ngoại lệ IllegalStateException. Khách hàng của mã này không mong đợi điều này: họ mong đợi được trả về true hoặc false . Điều này có thể dẫn đến lỗi trong chương trình.

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

Đặc trưng bởi tuyên bố sau: Không nên ép buộc khách hàng triển khai các phương thức mà họ sẽ không sử dụng . Nguyên tắc tách giao diện gợi ý rằng các giao diện quá “dày” cần được chia thành các giao diện nhỏ hơn và cụ thể hơn, để khách hàng có giao diện nhỏ chỉ biết về các phương pháp cần thiết cho công việc của họ. Kết quả là, khi thay đổi phương thức giao diện, các máy khách không sử dụng phương thức này sẽ không thay đổi. Hãy xem một ví dụ. Nhà phát triển Alex đã tạo giao diện "báo cáo" và thêm hai phương thức: generateExcel()generatedPdf(). Bây giờ Khách hàng A muốn sử dụng giao diện này nhưng anh ta chỉ có ý định sử dụng báo cáo PDF chứ không phải Excel. Liệu anh ấy có hài lòng với chức năng này không? KHÔNG. Anh ta sẽ phải thực hiện hai phương pháp, một trong số đó phần lớn là không cần thiết và chỉ tồn tại được nhờ Alex, nhà thiết kế phần mềm. Máy khách sẽ sử dụng giao diện khác hoặc để trống trường Excel. Vậy giải pháp là gì? Nó bao gồm việc chia giao diện hiện có thành hai giao diện nhỏ hơn. Một là báo cáo ở định dạng PDF, hai là báo cáo ở định dạng Excel. Điều này sẽ cung cấp cho người dùng cơ hội chỉ sử dụng các chức năng cần thiết cho mình.

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

Nguyên tắc SOLID này trong Java được mô tả như sau: các phần phụ thuộc trong hệ thống được xây dựng trên cơ sở trừu tượng hóa . Các mô-đun cấp cao nhất độc lập với các mô-đun cấp thấp hơn. Sự trừu tượng không nên phụ thuộc vào chi tiết. Chi tiết phải phụ thuộc vào sự trừu tượng. Phần mềm cần được thiết kế sao cho các mô-đun khác nhau có thể tự chủ và kết nối với nhau bằng cách sử dụng tính trừu tượng. Một ứng dụng cổ điển của nguyên tắc này là Spring framework. Trong khung Spring, 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 khép kín đến mức có thể được sử dụng dễ dàng trong các mô-đun phần mềm khác ngoài khung công tác Spring. Điều này đạt được thông qua 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 một bản tóm tắt có thể được sử dụng trong mô-đun khác. Hãy thử chứng minh điều này bằng một ví dụ. Nói về nguyên tắc chịu trách nhiệm duy nhất, chúng tôi đã xem xét một số OrderProcessor. Chúng ta hãy xem xét lại 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, lớp của chúng tôi OrderProcessorphụ thuộc vào hai lớp cụ thể MySQLOrderRepositoryConfirmationEmailSender. Chúng tôi cũng trình bày mã cho các lớp này:
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
    }
}
Những lớp này không được gọi là trừu tượng. Và từ quan điểm của nguyên tắc DIP, sẽ đúng hơn nếu bắt đầu bằng cách tạo ra một số phần trừu tượng cho phép chúng ta hoạt động với chúng trong tương lai, thay vì triển khai cụ thể. Hãy tạo hai giao diện MailSenderOrderRepository, sẽ trở thành phần trừu tượng của chúng ta:
public interface MailSender {
    void sendConfirmationEmail(Order order);
}

public interface OrderRepository {
    boolean save(Order order);
}
Bây giờ hãy triển khai các giao diện này trong các lớp đã sẵn sàng cho việc này:
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;
    }
}
Chúng tôi đã thực hiện công việc chuẩn bị để lớp học của chúng tôi OrderProcessorkhô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. Hãy thực hiện các thay đổi đối với nó bằng cách giới thiệu các phần phụ thuộc của chúng ta trong 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);
        }
    }
}
Lớp của chúng ta bây giờ phụ thuộc vào sự trừu tượng hơn là việc triển khai cụ thể. Bạn có thể dễ dàng thay đổi hành vi của nó bằng cách thêm phần phụ thuộc mong muốn vào thời điểm phiên bản được tạo OrderProcessor. Chúng tôi đã xem xét SOLID - nguyên tắc thiết kế trong Java. Tìm hiểu thêm về OOP nói chung, những điều cơ bản về ngôn ngữ lập trình này - không nhàm chán và có hàng trăm giờ thực hành - trong khóa học JavaRush. Đã đến lúc giải quyết một số vấn đề :) Năm nguyên tắc cơ bản của thiết kế lớp (SOLID) trong Java - 2
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION