
ระบบ
ลักษณะ ทั่วไปที่พึงประสงค์ของระบบคือ:- ความซับซ้อนน้อยที่สุด - ควรหลีกเลี่ยงโครงการที่ซับซ้อนเกินไป สิ่งสำคัญคือความเรียบง่ายและชัดเจน (ดีที่สุด = เรียบง่าย)
- ง่ายต่อการบำรุงรักษา - เมื่อสร้างแอปพลิเคชันคุณต้องจำไว้ว่าจะต้องได้รับการสนับสนุน (แม้ว่าจะไม่ใช่คุณก็ตาม) ดังนั้นรหัสควรมีความชัดเจนและชัดเจน
- การมีเพศสัมพันธ์ที่อ่อนแอคือจำนวนการเชื่อมต่อขั้นต่ำระหว่างส่วนต่าง ๆ ของโปรแกรม (การใช้หลักการ OOP สูงสุด)
- การนำกลับมาใช้ใหม่ได้ - การออกแบบระบบที่มีความสามารถในการนำชิ้นส่วนกลับมาใช้ใหม่ในแอปพลิเคชันอื่น
- ความสะดวกในการพกพา - ระบบจะต้องปรับให้เข้ากับสภาพแวดล้อมอื่นได้อย่างง่ายดาย
- สไตล์เดียว - การออกแบบระบบในรูปแบบเดียวในส่วนต่างๆ
- ความสามารถในการขยาย (scalability) - การปรับปรุงระบบโดยไม่รบกวนโครงสร้างพื้นฐาน (หากคุณเพิ่มหรือเปลี่ยนส่วนย่อย สิ่งนี้จะไม่ส่งผลกระทบต่อส่วนที่เหลือ)
ขั้นตอนการออกแบบระบบ
- ระบบซอฟต์แวร์ - การออกแบบแอพพลิเคชั่นในรูปแบบทั่วไป
- การแยกออกเป็นระบบย่อย/แพ็คเกจ - การกำหนดส่วนที่แยกออกจากกันอย่างมีเหตุผล และการกำหนดกฎของการโต้ตอบระหว่างส่วนเหล่านั้น
- การแบ่งระบบย่อยออกเป็นคลาส - การแบ่งส่วนของระบบออกเป็นคลาสและอินเทอร์เฟซเฉพาะ รวมถึงการกำหนดปฏิสัมพันธ์ระหว่างกัน
- การแบ่งคลาสออกเป็นวิธีการถือเป็นคำจำกัดความที่สมบูรณ์ของวิธีการที่จำเป็นสำหรับคลาส โดยขึ้นอยู่กับงานของคลาสนี้ การออกแบบวิธีการ - คำจำกัดความโดยละเอียดของการทำงานของแต่ละวิธี
หลักการและแนวคิดหลักในการออกแบบระบบ
สำนวนการเริ่มต้น Lazy แอปพลิเคชันจะไม่ใช้เวลาในการสร้างวัตถุจนกว่าจะมีการใช้งาน ซึ่งจะช่วยเร่งกระบวนการเริ่มต้นและลดภาระของตัวรวบรวมขยะ แต่คุณไม่ควรไปไกลเกินไปกับสิ่งนี้ เนื่องจากอาจนำไปสู่การละเมิดความเป็นโมดูลได้ การย้ายขั้นตอนการออกแบบทั้งหมดไปยังชิ้นส่วนเฉพาะ เช่น ชิ้นส่วนหลัก หรือชั้นเรียนที่ทำงานเหมือนกับโรงงานอาจคุ้มค่า ลักษณะหนึ่งของโค้ดที่ดีคือการไม่มีโค้ดสำเร็จรูปซ้ำๆ กันบ่อยครั้ง ตามกฎแล้วโค้ดดังกล่าวจะถูกวางไว้ในคลาสที่แยกจากกันเพื่อให้สามารถเรียกได้ในเวลาที่เหมาะสม AOP แยกกัน ฉันอยากจะพูดถึง การเขียนโปรแกรม เชิงแง่มุม นี่คือการเขียนโปรแกรมโดยการแนะนำตรรกะแบบ end-to-end นั่นคือโค้ดที่ทำซ้ำจะถูกใส่ลงในคลาส - แง่มุม และถูกเรียกเมื่อถึงเงื่อนไขบางประการ เช่น เมื่อเข้าถึงวิธีการที่มีชื่อเฉพาะหรือเข้าถึงตัวแปรประเภทใดประเภทหนึ่ง บางครั้งแง่มุมต่างๆ อาจทำให้เกิดความสับสน เนื่องจากยังไม่ชัดเจนในทันทีว่าโค้ดถูกเรียกจากที่ใด แต่อย่างไรก็ตาม นี่เป็นฟังก์ชันที่มีประโยชน์มาก โดยเฉพาะอย่างยิ่ง เมื่อทำการแคชหรือบันทึก: เราจะเพิ่มฟังก์ชันการทำงานนี้โดยไม่เพิ่มตรรกะเพิ่มเติมให้กับคลาสปกติ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ OAP ได้ ที่นี่ กฎ 4 ข้อสำหรับการออกแบบสถาปัตยกรรมที่เรียบง่ายตามแนวคิดของ Kent Beck- การแสดงออก - ความจำเป็นในการแสดงจุดประสงค์ที่ชัดเจนของชั้นเรียน บรรลุได้โดยการตั้งชื่อที่ถูกต้อง ขนาดเล็ก และการยึดมั่นในหลักการของความรับผิดชอบเดี่ยว (เราจะดูรายละเอียดเพิ่มเติมด้านล่าง)
- คลาสและวิธีการขั้นต่ำ - หากต้องการแบ่งคลาสออกเป็นขนาดเล็กและเป็นทิศทางเดียวเท่าที่เป็นไปได้ คุณอาจไปไกลเกินไป (ต่อต้านรูปแบบ - shotgunning) หลักการนี้เรียกร้องให้รักษาระบบให้มีขนาดกะทัดรัดและไม่ก้าวไกลจนเกินไป สร้างคลาสสำหรับการจามทุกครั้ง
- การขาดความซ้ำซ้อน - โค้ดพิเศษที่สร้างความสับสนเป็นสัญญาณของการออกแบบระบบที่ไม่ดี และถูกย้ายไปยังที่อื่น
- การดำเนินการทดสอบทั้งหมด - ระบบที่ผ่านการทดสอบทั้งหมดจะถูกควบคุม เนื่องจากการเปลี่ยนแปลงใดๆ อาจนำไปสู่ความล้มเหลวของการทดสอบ ซึ่งสามารถแสดงให้เราเห็นว่าการเปลี่ยนแปลงในตรรกะภายในของวิธีการยังนำไปสู่การเปลี่ยนแปลงในพฤติกรรมที่คาดหวังด้วย .
อินเตอร์เฟซ
บางทีขั้นตอนที่สำคัญที่สุดประการหนึ่งของการสร้างคลาสที่เพียงพอคือการสร้างอินเทอร์เฟซที่เพียงพอซึ่งจะแสดงนามธรรมที่ดีที่ซ่อนรายละเอียดการใช้งานของคลาส และในขณะเดียวกันก็จะแสดงกลุ่มของวิธีการที่สอดคล้องกันอย่างชัดเจน . ลองมาดูหลักการ SOLID ข้อใดข้อหนึ่งให้ละเอียดยิ่งขึ้น - การแยกส่วนต่อประสาน : ไคลเอนต์ (คลาส) ไม่ควรใช้วิธีการที่ไม่จำเป็นซึ่งพวกเขาจะไม่ใช้ นั่นคือหากเรากำลังพูดถึงการสร้างอินเทอร์เฟซด้วยจำนวนวิธีการขั้นต่ำที่มุ่งเป้าไปที่การทำงานเดียวของอินเทอร์เฟซนี้ (สำหรับฉันมันคล้ายกับความรับผิดชอบเดียว มาก ) จะดีกว่าถ้าสร้างขนาดเล็กกว่าสองสามวิธี แทนที่จะเป็นอินเทอร์เฟซที่ป่องเดียว โชคดีที่คลาสสามารถใช้อินเทอร์เฟซได้มากกว่าหนึ่งอินเทอร์เฟซ เช่นเดียวกับกรณีที่มีการสืบทอด คุณต้องจำเกี่ยวกับการตั้งชื่ออินเทอร์เฟซที่ถูกต้อง: ชื่อควรสะท้อนถึงงานของมันอย่างถูกต้องที่สุด และแน่นอนว่ายิ่งสั้นก็ยิ่งทำให้เกิดความสับสนน้อยลงเท่านั้น อยู่ที่ระดับอินเทอร์เฟซที่ความคิดเห็นสำหรับเอกสาร มักจะเขียน ซึ่งในทางกลับกันช่วยให้เราอธิบายรายละเอียดว่าวิธีการนี้ควรทำข้อโต้แย้งใดที่ใช้และจะส่งกลับอะไรระดับ

- ค่าคงที่คงที่สาธารณะ
- ค่าคงที่ส่วนตัวแบบคงที่
- ตัวแปรอินสแตนซ์ส่วนตัว
ขนาดชั้นเรียน
ตอนนี้ผมอยากจะพูดถึงขนาดชั้นเรียน
วัตถุ

การห่อหุ้ม
ก่อนอื่นเราจะพูดถึงหลักการข้อหนึ่งของ OOP - encapsulation ดังนั้น การซ่อนการนำไปใช้งานไม่ได้อยู่ที่การสร้างเลเยอร์วิธีการระหว่างตัวแปร (การจำกัดการเข้าถึงด้วยวิธีการเดียวอย่างไม่ต้องคิด เช่น getters และ setters ซึ่งไม่ดีเลย เนื่องจากจุดรวมของการห่อหุ้มหายไป) การซ่อนการเข้าถึงมีวัตถุประสงค์เพื่อสร้างนามธรรม นั่นคือ คลาสจัดเตรียมวิธีการที่เป็นรูปธรรมทั่วไปที่เราทำงานกับข้อมูลของเรา แต่ผู้ใช้ไม่จำเป็นต้องทราบอย่างชัดเจนว่าเราทำงานกับข้อมูลนี้อย่างไร มันใช้งานได้และก็ไม่เป็นไรกฎแห่งดีมีเตอร์
คุณยังสามารถพิจารณา Law of Demeter ได้: เป็นกฎชุดเล็กๆ ที่ช่วยจัดการความซับซ้อนในระดับชั้นเรียนและวิธี สมมติว่าเรามีวัตถุCar
และมีเมธอดmove(Object arg1, Object arg2)
- ตามกฎของ Demeter วิธีการนี้จำกัดอยู่เพียงการโทร:
- วิธีการของวัตถุนั้นเอง
Car
(กล่าวคือ สิ่งนี้); - วิธีการสร้างวัตถุใน
move
; - วิธีการส่งผ่านวัตถุเป็นข้อโต้แย้ง -
arg1
,arg2
; - วิธีการของวัตถุภายใน
Car
(เช่นเดียวกันนี้)
โครงสร้างข้อมูล
โครงสร้างข้อมูลคือชุดขององค์ประกอบที่เกี่ยวข้อง เมื่อพิจารณาวัตถุเป็นโครงสร้างข้อมูล มันคือชุดขององค์ประกอบข้อมูลที่ถูกประมวลผลโดยวิธีการ ซึ่งการมีอยู่ของสิ่งนั้นโดยปริยาย นั่นคือเป็นวัตถุที่มีวัตถุประสงค์เพื่อจัดเก็บและดำเนินการ (ประมวลผล) ข้อมูลที่เก็บไว้ ความแตกต่างที่สำคัญจากวัตถุทั่วไปคือวัตถุคือชุดของวิธีการที่ทำงานบนองค์ประกอบข้อมูลที่มีการมีอยู่โดยนัย คุณเข้าใจไหม? ในวัตถุทั่วไป ลักษณะหลักคือวิธีการ และตัวแปรภายในมุ่งเป้าไปที่การทำงานที่ถูกต้อง แต่ในโครงสร้างข้อมูลกลับเป็นอีกทางหนึ่ง: วิธีการสนับสนุนและช่วยทำงานกับองค์ประกอบที่เก็บไว้ ซึ่งเป็นองค์ประกอบหลักในที่นี้ โครงสร้างข้อมูลประเภทหนึ่งคือData Transfer Object (DTO ) นี่คือคลาสที่มีตัวแปรสาธารณะและไม่มีเมธอด (หรือเฉพาะเมธอดอ่าน/เขียนเท่านั้น) ที่ส่งข้อมูลเมื่อทำงานกับฐานข้อมูล ทำงานกับการแยกวิเคราะห์ข้อความจากซ็อกเก็ต ฯลฯ โดยทั่วไปแล้ว ข้อมูลในอ็อบเจ็กต์ดังกล่าวจะไม่ถูกเก็บไว้เป็นเวลานานและเป็น แปลงเกือบจะในทันทีเป็นเอนทิตีที่แอปพลิเคชันของเราทำงาน ในทางกลับกัน เอนทิตีก็เป็นโครงสร้างข้อมูลเช่นกัน แต่จุดประสงค์คือการมีส่วนร่วมในตรรกะทางธุรกิจในระดับต่างๆ ของแอปพลิเคชัน ในขณะที่ DTO คือการถ่ายโอนข้อมูลไปยัง/จากแอปพลิเคชัน ตัวอย่างดีทีโอ:@Setter
@Getter
@NoArgsConstructor
public class UserDto {
private long id;
private String firstName;
private String lastName;
private String email;
private String password;
}
ทุกอย่างดูชัดเจน แต่ที่นี่เราเรียนรู้เกี่ยวกับการมีอยู่ของลูกผสม ไฮบริดเป็นอ็อบเจ็กต์ที่มีวิธีการในการจัดการตรรกะที่สำคัญและจัดเก็บองค์ประกอบภายในและวิธีการเข้าถึง (รับ/ตั้งค่า) วัตถุดังกล่าวยุ่งเหยิงและทำให้ยากต่อการเพิ่มวิธีการใหม่ คุณไม่ควรใช้เนื่องจากยังไม่ชัดเจนว่ามีไว้เพื่ออะไร - จัดเก็บองค์ประกอบหรือดำเนินการตรรกะบางประเภท คุณสามารถอ่านเกี่ยวกับประเภทของออบเจ็กต์ที่เป็นไปได้ได้ที่ นี่
หลักการสร้างตัวแปร

- ตามหลักการแล้ว คุณควรประกาศและเริ่มต้นตัวแปรทันทีก่อนใช้งาน (แทนที่จะสร้างมันขึ้นมาแล้วลืมมันไป)
- เมื่อใดก็ตามที่เป็นไปได้ ให้ประกาศตัวแปรเป็นตัวแปรสุดท้ายเพื่อป้องกันไม่ให้ค่าเปลี่ยนแปลงหลังจากการกำหนดค่าเริ่มต้น
- อย่าลืมเกี่ยวกับตัวแปรตัวนับ (โดยปกติแล้วเราใช้พวกมันในการวนซ้ำบางประเภท
for
นั่นคือเราต้องไม่ลืมที่จะรีเซ็ตพวกมัน ไม่เช่นนั้นอาจทำให้ตรรกะทั้งหมดของเราเสียหายได้) - คุณควรพยายามเริ่มต้นตัวแปรในตัวสร้าง
- หากมีทางเลือกระหว่างการใช้ออบเจ็กต์ที่มีหรือไม่มีการอ้างอิง (
new SomeObject()
) ให้เลือกไม่มี ( ) เนื่องจากเมื่อใช้แล้วออบเจ็กต์นี้จะถูกลบออกในระหว่างการรวบรวมขยะครั้งถัดไปและจะไม่สิ้นเปลืองทรัพยากร - ทำให้อายุการใช้งานของตัวแปรสั้นที่สุดเท่าที่จะเป็นไปได้ (ระยะห่างระหว่างการสร้างตัวแปรและการเข้าถึงครั้งล่าสุด)
- เริ่มต้นตัวแปรที่ใช้ในลูปทันทีก่อนลูป แทนที่จะเริ่มต้นที่จุดเริ่มต้นของวิธีการที่มีการวนซ้ำ
- เริ่มต้นด้วยขอบเขตที่จำกัดที่สุดเสมอและขยายขอบเขตหากจำเป็นเท่านั้น (คุณควรพยายามทำให้ตัวแปรเป็นแบบโลคัลมากที่สุด)
- ใช้แต่ละตัวแปรเพื่อจุดประสงค์เดียวเท่านั้น
- หลีกเลี่ยงตัวแปรที่มีความหมายที่ซ่อนอยู่ (ตัวแปรถูกแยกระหว่างสองงาน ซึ่งหมายความว่าประเภทของมันไม่เหมาะสำหรับการแก้ปัญหาอย่างใดอย่างหนึ่ง)

วิธีการ

-
กฎข้อแรกคือความกะทัดรัด ตามหลักการแล้ว วิธีการหนึ่งไม่ควรเกิน 20 บรรทัด ดังนั้น หากวิธีสาธารณะ "ขยาย" อย่างมีนัยสำคัญ คุณต้องคิดถึงการย้ายตรรกะที่แยกออกไปเป็นวิธีการส่วนตัว
-
กฎข้อที่สองคือบล็อกในคำสั่ง
if
,else
และwhile
อื่นๆ ไม่ควรซ้อนกันอย่างมาก ซึ่งจะช่วยลดความสามารถในการอ่านโค้ดได้อย่างมาก{}
ตามหลักการแล้ว การซ้อนไม่ควรเกินสองช่วงตึกขอแนะนำให้สร้างโค้ดในบล็อกเหล่านี้ให้กะทัดรัดและเรียบง่าย
-
กฎข้อที่สามคือวิธีการจะต้องดำเนินการเพียงครั้งเดียวเท่านั้น นั่นคือ ถ้าวิธีการใดใช้ตรรกะที่ซับซ้อนและหลากหลาย เราจะแบ่งวิธีการนั้นออกเป็นวิธีการย่อย เป็นผลให้วิธีการนั้นจะเป็นส่วนหน้าโดยมีจุดประสงค์เพื่อเรียกการดำเนินการอื่น ๆ ทั้งหมดตามลำดับที่ถูกต้อง
แต่จะเกิดอะไรขึ้นถ้าการดำเนินการดูเหมือนง่ายเกินไปที่จะสร้างวิธีการแยกต่างหาก? ใช่ บางครั้งอาจดูเหมือนการยิงนกกระจอกออกจากปืนใหญ่ แต่วิธีการเล็กๆ น้อยๆ ให้ประโยชน์หลายประการ:
- อ่านโค้ดได้ง่ายขึ้น
- วิธีการมีแนวโน้มที่จะซับซ้อนมากขึ้นในระหว่างการพัฒนา และหากวิธีการนั้นง่ายในตอนแรก การทำให้ฟังก์ชันการทำงานซับซ้อนขึ้นก็จะง่ายขึ้นเล็กน้อย
- การซ่อนรายละเอียดการใช้งาน
- อำนวยความสะดวกในการใช้โค้ดซ้ำ
- ความน่าเชื่อถือของโค้ดที่สูงขึ้น
-
กฎด้านล่างคือควรอ่านโค้ดจากบนลงล่าง: ยิ่งต่ำ ยิ่งมีความลึกของตรรกะมากขึ้น และในทางกลับกัน ยิ่งสูงเท่าใด วิธีการที่เป็นนามธรรมก็จะมากขึ้นเท่านั้น ตัวอย่างเช่น คำสั่ง switch ค่อนข้างไม่กระชับและไม่เป็นที่พึงปรารถนา แต่ถ้าคุณทำไม่ได้โดยไม่ต้องใช้สวิตช์ คุณควรพยายามย้ายคำสั่งให้ต่ำที่สุดไปยังวิธีระดับต่ำสุด
-
อาร์กิวเมนต์ของวิธีการ - มีกี่ข้อในอุดมคติ? ตามหลักการแล้วไม่มีเลย)) แต่สิ่งนั้นจะเกิดขึ้นจริงหรือ? อย่างไรก็ตาม คุณควรพยายามมีให้น้อยที่สุดเท่าที่จะเป็นไปได้ เพราะยิ่งมีน้อยเท่าไร การใช้วิธีนี้ก็จะง่ายขึ้นและทดสอบได้ง่ายขึ้นด้วย หากมีข้อสงสัย ให้ลองเดาสถานการณ์ทั้งหมดสำหรับการใช้วิธีการที่มีอาร์กิวเมนต์อินพุตจำนวนมาก
-
แยกกัน ฉันต้องการเน้นวิธีการที่มีการตั้งค่าสถานะบูลีน เป็นอาร์กิวเมนต์อินพุต เนื่องจากนี่บอกเป็นนัยโดยธรรมชาติว่าวิธีนี้ใช้การดำเนินการมากกว่าหนึ่งรายการ (หากเป็นจริงแล้วหนึ่งรายการ เท็จ - อีกรายการหนึ่ง) ตามที่ฉันเขียนไว้ข้างต้น สิ่งนี้ไม่ดี และควรหลีกเลี่ยงหากเป็นไปได้
-
ถ้าเมธอดมีอาร์กิวเมนต์ขาเข้าจำนวนมาก (ค่าสูงสุดคือ 7 แต่คุณควรพิจารณาหลังจาก 2-3) คุณจะต้องจัดกลุ่มอาร์กิวเมนต์บางส่วนในออบเจ็กต์ที่แยกจากกัน
-
หากมีวิธีการที่คล้ายกันหลายวิธี (โอเวอร์โหลด)จะต้องส่งพารามิเตอร์ที่คล้ายกันในลำดับเดียวกัน ซึ่งจะเพิ่มความสามารถในการอ่านและการใช้งาน
-
เมื่อคุณส่งพารามิเตอร์ไปยังเมธอด คุณต้องแน่ใจว่าพวกมันจะถูกใช้ทั้งหมด ไม่อย่างนั้นอาร์กิวเมนต์มีไว้เพื่ออะไร? ตัดมันออกจากอินเทอร์เฟซก็แค่นั้นแหละ
-
try/catch
มันไม่ได้ดูสวยงามนักโดยธรรมชาติ ดังนั้นการย้ายที่ดีคือย้ายมันไปไว้ในวิธีที่แยกจากกันระดับกลาง (วิธีจัดการกับข้อยกเว้น):public void exceptionHandling(SomeObject obj) { try { someMethod(obj); } catch (IOException e) { e.printStackTrace(); } }

GO TO FULL VERSION