JavaRush /จาวาบล็อก /Random-TH /หลักการพื้นฐานห้าประการของการออกแบบคลาส (SOLID) ใน Java
Ve4niY
ระดับ

หลักการพื้นฐานห้าประการของการออกแบบคลาส (SOLID) ใน Java

เผยแพร่ในกลุ่ม
คลาสคือบล็อกที่ใช้สร้างแอปพลิเคชัน เช่นเดียวกับอิฐในอาคาร ชั้นเรียนที่เขียนไม่ดีอาจทำให้เกิดปัญหาได้สักวันหนึ่ง หลักการพื้นฐานห้าประการของการออกแบบคลาส (SOLID) ใน Java - 1หากต้องการทราบว่าชั้นเรียนเขียนถูกต้องหรือไม่ คุณสามารถตรวจสอบ "มาตรฐานคุณภาพ" ได้ ใน Java สิ่งเหล่านี้เรียกว่าหลักการ SOLID มาพูดถึงพวกเขากันดีกว่า

หลักการที่มั่นคงใน Java

SOLID เป็นตัวย่อที่เกิดจากอักษรตัวใหญ่ของหลักการห้าข้อแรกของ OOP และการออกแบบ หลักการนี้ถูกประดิษฐ์ขึ้นโดย Robert Martin ในช่วงต้นทศวรรษ 2000 และต่อมา Michael Feathers ได้เป็นผู้ประดิษฐ์ตัวย่อขึ้นมา หลักการของ SOLID มีดังนี้:
  1. หลักการความรับผิดชอบเดียว
  2. เปิดหลักการปิด
  3. หลักการทดแทนของ Liskov
  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
    }
}
โมดูลดังกล่าวอาจมีการเปลี่ยนแปลงด้วยเหตุผลสามประการ ประการแรก ตรรกะการประมวลผลคำสั่งซื้ออาจแตกต่างกัน ประการที่สอง วิธีการบันทึก (ประเภทฐานข้อมูล) ประการที่สาม วิธีการส่งจดหมายยืนยัน (เช่น แทนที่จะส่งอีเมล คุณต้องส่ง SMS) หลักการความรับผิดชอบเดียวบ่งบอกว่าแง่มุมทั้งสามของปัญหานี้ แท้จริงแล้วเป็นความรับผิดชอบที่แตกต่างกันสามประการ ซึ่งหมายความว่าจะต้องอยู่ในคลาสหรือโมดูลที่แตกต่างกัน การรวมหลายเอนทิตีที่อาจเปลี่ยนแปลงในเวลาที่ต่างกันและด้วยเหตุผลที่แตกต่างกันถือเป็นการตัดสินใจออกแบบที่ไม่ดี เป็นการดีกว่ามากที่จะแบ่งโมดูลออกเป็นสามโมดูลแยกกัน ซึ่งแต่ละโมดูลจะทำหน้าที่เดียว:
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ที่ส่งคืนtrueหรือfalse :
public class OrderStockValidator {

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

        return true;
    }
}
สมมติว่าคำสั่งซื้อบางรายการจำเป็นต้องได้รับการตรวจสอบแตกต่างออกไป: ตรวจสอบว่าสินค้าทั้งหมดในคำสั่งซื้อมีในสต็อกหรือไม่ และสินค้าทั้งหมดได้รับการบรรจุหีบห่อแล้วหรือไม่ เมื่อต้องการทำเช่นนี้ เราได้ขยายคลาสOrderStockValidatorด้วย class 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 เพราะแทนที่จะส่งคืนfalseหากคำสั่งซื้อไม่ผ่านการตรวจสอบ วิธีการของเรากลับส่งข้อIllegalStateExceptionยกเว้น ลูกค้าของรหัสนี้ไม่คาดหวังสิ่งนี้: พวกเขาคาดหวังว่าจะ ถูกส่งกลับ จริงหรือเท็จ ซึ่งอาจนำไปสู่ข้อผิดพลาดในโปรแกรมได้

หลักการแยกส่วนต่อประสาน (ISP)

โดดเด่นด้วยข้อความต่อไปนี้: ลูกค้าไม่ควรถูกบังคับให้ใช้วิธีการที่พวกเขาจะไม่ใช้ หลักการแยกอินเทอร์เฟซแนะนำว่าอินเทอร์เฟซที่ "หนา" เกินไปจะต้องแบ่งออกเป็นอินเทอร์เฟซที่เล็กกว่าและเฉพาะเจาะจงมากขึ้น เพื่อให้ไคลเอนต์ของอินเทอร์เฟซขนาดเล็กรู้เฉพาะวิธีการที่จำเป็นสำหรับการทำงานเท่านั้น ด้วยเหตุนี้ เมื่อเปลี่ยนวิธีอินเทอร์เฟซ ไคลเอนต์ที่ไม่ใช้วิธีการนี้ไม่ควรเปลี่ยนแปลง ลองดูตัวอย่าง นักพัฒนา Alex ได้สร้างอินเทอร์เฟซ "รายงาน" และเพิ่มสองวิธี: generateExcel()และgeneratedPdf(). ตอนนี้ลูกค้า A ต้องการใช้อินเทอร์เฟซนี้ แต่เขาตั้งใจจะใช้รายงาน PDF เท่านั้น ไม่ใช่ Excel เขาจะพอใจกับฟังก์ชั่นนี้หรือไม่? เลขที่ เขาจะต้องใช้สองวิธี หนึ่งในนั้นไม่จำเป็นมากและมีอยู่จริงต้องขอบคุณ Alex นักออกแบบซอฟต์แวร์เท่านั้น ลูกค้าจะใช้อินเทอร์เฟซอื่นหรือปล่อยฟิลด์ Excel ว่างไว้ แล้วทางแก้คืออะไร? ประกอบด้วยการแบ่งอินเทอร์เฟซที่มีอยู่ออกเป็นสองส่วนย่อย หนึ่งคือรายงานในรูปแบบ PDF และสองคือรายงานในรูปแบบ Excel สิ่งนี้จะทำให้ผู้ใช้มีโอกาสใช้เฉพาะฟังก์ชันการทำงานที่จำเป็นสำหรับเขาเท่านั้น

หลักการผกผันการพึ่งพา (DIP)

หลักการ SOLID ใน Java นี้อธิบายไว้ดังนี้: การพึ่งพาภายในระบบถูกสร้างขึ้นบนพื้นฐานของนามธรรม โมดูลระดับบนสุดไม่ขึ้นอยู่กับโมดูลระดับล่าง นามธรรมไม่ควรขึ้นอยู่กับรายละเอียด รายละเอียดจะต้องขึ้นอยู่กับนามธรรม ซอฟต์แวร์จำเป็นต้องได้รับการออกแบบเพื่อให้โมดูลต่างๆ เป็นอิสระและเชื่อมต่อกันโดยใช้นามธรรม การประยุกต์ใช้หลักการนี้แบบคลาสสิกคือ Spring Framework ภายในกรอบงาน Spring โมดูลทั้งหมดจะถูกนำไปใช้เป็นส่วนประกอบแยกกันซึ่งสามารถทำงานร่วมกันได้ พวกมันมีความสมบูรณ์ในตัวเองจนสามารถนำไปใช้ได้อย่างง่ายดายในโมดูลซอฟต์แวร์อื่น ๆ นอกเหนือจาก Spring Framework สิ่งนี้เกิดขึ้นได้จากการพึ่งพาหลักการปิดและเปิด โมดูลทั้งหมดให้การเข้าถึงเฉพาะนามธรรมที่สามารถใช้ในโมดูลอื่นได้ ลองสาธิตสิ่งนี้ด้วยตัวอย่าง เมื่อพูดถึงหลักความรับผิดชอบแต่เพียงผู้เดียว เราพิจารณาบาง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อยู่กับคลาสเฉพาะสองคลาสMySQLOrderRepositoryและ ConfirmationEmailSenderเรายังนำเสนอโค้ดสำหรับคลาสเหล่านี้ด้วย:
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
    }
}
คลาสเหล่านี้อยู่ไกลจากการถูกเรียกว่านามธรรม และจากมุมมองของหลักการกรมทรัพย์สินทางปัญญา การเริ่มต้นด้วยการสร้างนามธรรมบางอย่างที่จะช่วยให้เราดำเนินการกับสิ่งเหล่านั้นได้ในอนาคต แทนที่จะนำไปใช้งานเฉพาะเจาะจงจะถูกต้องมากกว่า มาสร้างอินเทอร์เฟซสองอันMailSenderและOrderRepositoryซึ่งจะกลายเป็นนามธรรมของเรา:
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คุณสามารถเปลี่ยนลักษณะการทำงานได้อย่างง่ายดายโดยการฉีดการขึ้นต่อกันที่ต้องการในขณะ ที่สร้างอินสแตนซ์ เราดูที่ SOLID - หลักการออกแบบใน Java ข้อมูลเพิ่มเติมเกี่ยวกับ OOP โดยทั่วไป พื้นฐานของภาษาการเขียนโปรแกรมนี้ ซึ่งไม่น่าเบื่อและด้วยการฝึกฝนหลายร้อยชั่วโมงในหลักสูตร JavaRush ถึงเวลาที่จะแก้ไขปัญหาบางอย่าง :) หลักการพื้นฐานห้าประการของการออกแบบคลาส (SOLID) ใน Java - 2
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION