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