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

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