โค้ดยิม/จาวาบล็อก/สุ่ม/SOLID: หลักการพื้นฐานห้าประการของการออกแบบคลาสใน Java
John Squirrels
ระดับ
San Francisco

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

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

หลักการ SOLID ใน Java

SOLID เป็นตัวย่อที่สร้างจากตัวพิมพ์ใหญ่ของหลักการห้าข้อแรกของ OOP และการออกแบบคลาส หลักการนี้แสดงโดย Robert Martin ในช่วงต้นทศวรรษ 2000 และต่อมา Michael Feathers ก็แนะนำตัวย่อ นี่คือหลักการที่มั่นคง:
  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()วิธีการที่คืนค่าจริงหรือเท็จ :
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 เพราะแทนที่จะส่งคืนค่าเท็จหากคำสั่งไม่ผ่านการตรวจสอบความถูกต้อง วิธีการของเราจะส่งไฟล์IllegalStateException. ลูกค้าที่ใช้รหัส นี้ไม่คาดหวังสิ่งนี้: พวกเขาคาดหวังค่าที่ส่งคืนจริงหรือเท็จ สิ่งนี้สามารถนำไปสู่ข้อผิดพลาดรันไทม์

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

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

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

ใน Java หลักการ SOLID นี้อธิบายไว้ดังนี้:การพึ่งพาภายในระบบสร้างขึ้นจากนามธรรม. โมดูลระดับสูงไม่ได้ขึ้นอยู่กับโมดูลระดับล่าง สิ่งที่เป็นนามธรรมไม่ควรขึ้นอยู่กับรายละเอียด รายละเอียดควรขึ้นอยู่กับนามธรรม ซอฟต์แวร์จำเป็นต้องได้รับการออกแบบเพื่อให้โมดูลต่างๆ การประยุกต์ใช้หลักการนี้แบบคลาสสิกคือ Spring Framework ใน Spring Framework โมดูลทั้งหมดจะถูกนำไปใช้เป็นส่วนประกอบแยกต่างหากที่สามารถทำงานร่วมกันได้ พวกมันเป็นอิสระมากจนสามารถใช้งานได้อย่างง่ายดายในโมดูลโปรแกรมอื่นนอกเหนือจาก 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 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
    }
}
ชั้นเรียนเหล่านี้ห่างไกลจากสิ่งที่เราเรียกว่าสิ่งที่เป็นนามธรรม และจากมุมมองของหลักการผกผันการพึ่งพา จะเป็นการดีกว่าหากเริ่มต้นด้วยการสร้างสิ่งที่เป็นนามธรรมซึ่งเราสามารถใช้งานได้ในอนาคต แทนที่จะนำไปใช้เฉพาะเจาะจง มาสร้างสองอินเทอร์เฟซ: 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();

        // 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ที่สร้างวัตถุ เราได้ตรวจสอบหลักการออกแบบ SOLID ใน Java คุณจะได้เรียนรู้เพิ่มเติมเกี่ยวกับ OOP โดยทั่วไปและพื้นฐานของการเขียนโปรแกรม Java — ไม่มีอะไรน่าเบื่อและฝึกฝนหลายร้อยชั่วโมง — ในหลักสูตร CodeGym ได้เวลาแก้ปัญหาบางอย่างแล้ว :)
ความคิดเห็น
  • เป็นที่นิยม
  • ใหม่
  • เก่า
คุณต้องลงชื่อเข้าใช้เพื่อแสดงความคิดเห็น
หน้านี้ยังไม่มีความคิดเห็นใด ๆ