การเรียนรู้ OOP: คู่มือปฏิบัติสำหรับการสืบทอด อินเตอร์เฟส และคลาสนามธรรม
เผยแพร่แล้ว: 2022-03-10เท่าที่ฉันสามารถบอกได้ เป็นเรื่องปกติที่จะเจอเนื้อหาด้านการศึกษาในด้านการพัฒนาซอฟต์แวร์ซึ่งให้ข้อมูลเชิงทฤษฎีและเชิงปฏิบัติผสมกันอย่างเหมาะสม ถ้าฉันให้เดาว่าทำไม ฉันคิดว่าเป็นเพราะบุคคลที่เน้นทฤษฎีมักจะเข้ามาสอน และบุคคลที่มุ่งเน้นไปที่ข้อมูลที่เป็นประโยชน์มักจะได้รับเงินเพื่อแก้ปัญหาเฉพาะ โดยใช้ภาษาและเครื่องมือเฉพาะ
แน่นอนว่านี่เป็นการสรุปแบบกว้างๆ แต่ถ้าเรายอมรับมันสั้นๆ เพื่อการโต้แย้ง ก็คงมีคนจำนวนมาก (ไม่ใช่ทุกคน) ที่รับบทบาทเป็นครู มักจะยากจนหรือไร้ความสามารถโดยสิ้นเชิง ของการอธิบายความรู้เชิงปฏิบัติที่เกี่ยวข้องกับแนวคิดเฉพาะ
ในบทความนี้ ฉันจะพยายามอย่างเต็มที่เพื่อหารือเกี่ยวกับกลไกหลักสามประการที่คุณจะพบในภาษาการเขียนโปรแกรมเชิงวัตถุ (OOP) ส่วนใหญ่: การ สืบทอด อินเทอร์เฟซ (หรือโปรโตคอลที่เรียกว่า โปรโตคอล ) และ คลาสนามธรรม แทนที่จะให้คำอธิบายทางเทคนิคและด้วยวาจาที่ซับซ้อนแก่คุณเกี่ยวกับกลไก แต่ละ อย่าง ฉันจะทำให้ดีที่สุดเพื่อมุ่งเน้นไปที่สิ่งที่พวกเขา ทำ และ เมื่อใด ควรใช้กลไกเหล่านี้
อย่างไรก็ตาม ก่อนพูดถึงพวกเขาทีละคน ฉันต้องการพูดคุยสั้น ๆ ว่าการให้คำอธิบายที่ถูกต้องตามหลักทฤษฎีแต่ก็ไร้ประโยชน์ในทางปฏิบัติหมายความว่าอย่างไร ความหวังของฉันคือการที่คุณสามารถใช้ข้อมูลนี้เพื่อช่วยคุณกลั่นกรองแหล่งข้อมูลทางการศึกษาต่างๆ และเพื่อหลีกเลี่ยงการตำหนิตัวเองเมื่อสิ่งต่างๆ ไม่สมเหตุสมผล
ระดับความรู้ที่แตกต่างกัน
รู้จักชื่อ
การรู้ชื่อของบางสิ่งอาจเป็นรูปแบบการรู้ที่ตื้นที่สุด อันที่จริง ชื่อมักจะมีประโยชน์เฉพาะในขอบเขตที่หลายคนมักใช้เพื่ออ้างถึงสิ่งเดียวกัน และ/หรือช่วยอธิบายสิ่งนั้น น่าเสียดาย เนื่องจากใครก็ตามที่ใช้เวลาในด้านนี้ค้นพบ หลายคนใช้ชื่อที่แตกต่างกันสำหรับสิ่งเดียวกัน (เช่น ส่วนต่อประสาน และ โปรโตคอล ) ชื่อเดียวกันสำหรับสิ่งต่าง ๆ (เช่น โมดูล และ ส่วนประกอบ ) หรือชื่อที่ลึกลับสำหรับ จุดที่ไร้สาระ (เช่น Monad ) ในที่สุด ชื่อเป็นเพียงตัวชี้ (หรือการอ้างอิง) ไปยังแบบจำลองทางจิต และอาจมีประโยชน์ในระดับต่างๆ
ในการทำให้สาขาวิชานี้ยากยิ่งขึ้นในการศึกษา ข้าพเจ้าคงเดาไม่ถูกว่าสำหรับบุคคลส่วนใหญ่ การเขียนโค้ด (หรืออย่างน้อยก็) เป็นประสบการณ์ที่ไม่เหมือนใคร ความซับซ้อนยิ่งกว่านั้นคือการทำความเข้าใจว่าโค้ดนั้นถูกคอมไพล์เป็นภาษาเครื่องได้อย่างไร และแสดงในความเป็นจริงทางกายภาพเป็นชุดของแรงกระตุ้นทางไฟฟ้าที่เปลี่ยนแปลงไปตามกาลเวลา แม้ว่าเราสามารถจำชื่อของกระบวนการ แนวคิด และกลไกที่ใช้ในโปรแกรมได้ แต่ก็ไม่รับประกันว่าแบบจำลองทางจิตที่เราสร้างขึ้นสำหรับสิ่งเหล่านี้จะสอดคล้องกับแบบจำลองของบุคคลอื่น นับประสาว่าจะถูกต้องตามความเป็นจริงหรือไม่
ด้วยเหตุผลเหล่านี้ ควบคู่ไปกับความจริงที่ว่า ฉันไม่มีความจำที่ดีโดยธรรมชาติสำหรับศัพท์แสง ฉันถือว่าชื่อเป็นส่วนสำคัญน้อยที่สุดในการรู้อะไรบางอย่าง ไม่ได้หมายความว่าชื่อจะไม่มีประโยชน์ แต่ในอดีต ฉันได้เรียนรู้และใช้รูปแบบการออกแบบมากมายในโครงการของฉัน เพียงเพื่อเรียนรู้เกี่ยวกับเดือนของชื่อที่ใช้กันทั่วไป หรือแม้แต่หลายปีต่อมา
รู้ความหมายทางวาจาและความคล้ายคลึงกัน
คำจำกัดความทางวาจาเป็นจุดเริ่มต้นตามธรรมชาติสำหรับการอธิบายแนวคิดใหม่ อย่างไรก็ตาม เช่นเดียวกับชื่อ อาจมีระดับความมีประโยชน์และความเกี่ยวข้องที่แตกต่างกันไป ส่วนใหญ่ขึ้นอยู่กับเป้าหมายสุดท้ายของผู้เรียน ปัญหาที่พบบ่อยที่สุดที่ฉันเห็นในคำจำกัดความด้วยวาจาคือการสันนิษฐานว่าความรู้โดยทั่วไปอยู่ในรูปแบบของศัพท์แสง
ตัวอย่างเช่น สมมติว่าฉันต้องอธิบายว่า เธรด นั้นเหมือนกับ กระบวนการ มาก ยกเว้นว่า เธรด นั้นใช้ พื้นที่ที่อยู่ เดียวกันของ กระบวนการ ที่กำหนด สำหรับผู้ที่คุ้นเคยกับ กระบวนการ และ ช่องว่างที่อยู่อยู่ แล้ว ฉันได้ระบุโดยพื้นฐานแล้วว่า เธรด สามารถเชื่อมโยงกับความเข้าใจใน กระบวนการ ของพวกเขา (กล่าวคือ มีคุณลักษณะหลายอย่างที่เหมือนกัน) แต่สามารถแยกความแตกต่างตามลักษณะเฉพาะที่แตกต่างกันได้
สำหรับคนที่ไม่มีความรู้นั้น ฉันก็ไม่มีเหตุผล และที่แย่ที่สุดคือทำให้ผู้เรียนรู้สึกไม่เพียงพอในทางใดทางหนึ่งเพราะไม่รู้สิ่งที่ ฉันคิดว่า พวกเขาควรรู้ ด้วยความเป็นธรรม สิ่งนี้เป็นที่ยอมรับได้หากผู้เรียนของคุณควรจะมีความรู้ดังกล่าวจริงๆ (เช่น การสอนนักศึกษาระดับบัณฑิตศึกษาหรือนักพัฒนาที่มีประสบการณ์) แต่ฉันคิดว่านี่เป็นความล้มเหลวครั้งใหญ่ในเนื้อหาระดับเกริ่นนำใดๆ
บ่อยครั้งเป็นเรื่องยากมากที่จะให้คำจำกัดความของแนวคิดด้วยวาจาที่ดี เมื่อมันไม่เหมือนสิ่งอื่นใดที่ผู้เรียนเคยเห็นมาก่อน ในกรณีนี้ เป็นสิ่งสำคัญมากสำหรับครูที่จะเลือกการเปรียบเทียบที่มีแนวโน้มว่าคนทั่วไปจะคุ้นเคย และมีความเกี่ยวข้องตราบเท่าที่มันสื่อถึงคุณสมบัติเดียวกันหลายประการของแนวคิด
ตัวอย่างเช่น เป็นสิ่งสำคัญอย่างยิ่งที่นักพัฒนาซอฟต์แวร์จะต้องเข้าใจความหมายเมื่อเอนทิตีซอฟต์แวร์ (ส่วนต่างๆ ของโปรแกรม) มีความสัมพันธ์กัน อย่าง แน่นหนา เมื่อสร้างเพิงสำหรับสวน ช่างไม้รุ่นเยาว์อาจคิดว่ามัน เร็ว และ ง่ายกว่า ที่จะประกอบเข้าด้วยกันโดยใช้ตะปูแทนการใช้ตะปู สิ่งนี้จะเป็นจริงจนกระทั่งถึงจุดที่เกิดความผิดพลาด หรือการเปลี่ยนแปลงในการออกแบบเพิงในสวนจำเป็นต้องสร้างส่วนหนึ่งของเพิงขึ้นใหม่
ณ จุดนี้ การตัดสินใจใช้ตะปูเพื่อยึดส่วนต่างๆ ของสวนเข้าด้วยกัน อย่างแน่นหนา ทำให้ขั้นตอนการก่อสร้างโดยรวมยากขึ้น มีแนวโน้มว่าจะช้าลง และการตอกตะปูด้วยค้อนเสี่ยงต่อการทำลายโครงสร้าง ในทางกลับกัน สกรูอาจใช้เวลาในการประกอบเพิ่มขึ้นเล็กน้อย แต่ถอดออกได้ง่าย และเสี่ยงเพียงเล็กน้อยที่จะเกิดความเสียหายต่อส่วนต่างๆ ของโรงเก็บของในบริเวณใกล้เคียง นี่คือสิ่งที่ฉัน หมาย ถึงโดย โดยปกติ มีหลายกรณีที่คุณเพียงแค่ต้องการเล็บ แต่การตัดสินใจนั้นควรได้รับคำแนะนำจากการคิดอย่างมีวิจารณญาณและประสบการณ์
ดังที่ฉันจะกล่าวถึงในรายละเอียดในภายหลัง มีกลไกที่แตกต่างกันสำหรับการเชื่อมต่อส่วนต่างๆ ของโปรแกรมเข้าด้วยกันซึ่งมีระดับการ มีเพศสัมพันธ์ ที่แตกต่างกัน เช่นเดียวกับ เล็บ และ สกรู แม้ว่าการเปรียบเทียบของฉันอาจช่วยให้คุณเข้าใจความหมายของคำศัพท์ที่สำคัญยิ่งนี้ แต่ฉันยังไม่ได้ให้แนวคิดใดๆ แก่คุณว่าจะนำไปใช้อย่างไรนอกบริบทของการสร้างเพิงในสวน สิ่งนี้นำฉันไปสู่ความรู้ที่สำคัญที่สุดและกุญแจสำคัญในการทำความเข้าใจแนวคิดที่คลุมเครือและยากในทุกสาขา แม้ว่าเราจะยึดติดกับการเขียนโค้ดในบทความนี้
รู้ในรหัส
ในความคิดของฉัน อย่างเคร่งครัดเกี่ยวกับการพัฒนาซอฟต์แวร์ รูปแบบการนำเข้ามากที่สุดของการรู้แนวคิดมาจากความสามารถในการใช้ในโค้ดของแอปพลิเคชันที่ใช้งานได้ รูปแบบของการรู้นี้สามารถบรรลุได้โดยการเขียนโค้ดจำนวนมากและแก้ปัญหาต่างๆ มากมาย ไม่จำเป็นต้องรวมชื่อศัพท์แสงและคำจำกัดความด้วยวาจา
จากประสบการณ์ของฉันเอง ฉันจำได้ว่าการแก้ปัญหาการสื่อสารกับฐานข้อมูลระยะไกล และฐานข้อมูลภายในผ่าน อินเทอร์เฟซ เดียว (คุณจะรู้ว่ามันหมายถึงอะไรในไม่ช้าถ้าคุณยังไม่ได้ทำ); แทนที่จะเป็นไคลเอนต์ (คลาสใดก็ตามที่พูดกับ อินเตอร์เฟส ) จำเป็นต้องเรียกรีโมตและโลคัล (หรือแม้แต่ฐานข้อมูลทดสอบ) อย่างชัดเจน อันที่จริง ลูกค้าไม่รู้ว่ามีอะไรอยู่เบื้องหลังอินเทอร์เฟซ ดังนั้นฉันจึงไม่จำเป็นต้องเปลี่ยนอินเทอร์เฟซไม่ว่าจะทำงานในแอปที่ใช้งานจริงหรือในสภาพแวดล้อมการทดสอบ ประมาณหนึ่งปีหลังจากที่ฉันแก้ปัญหานี้ ฉันมาเจอคำว่า "Facade Pattern" และไม่นานหลังจากคำว่า "Repository Pattern" ซึ่งเป็นชื่อที่ผู้ใช้ทั้งสองใช้สำหรับโซลูชันที่อธิบายไว้ก่อนหน้านี้
บทนำทั้งหมดนี้หวังว่าจะทำให้กระจ่างถึงข้อบกพร่องบางประการซึ่งมักเกิดขึ้นในการอธิบายหัวข้อต่างๆ เช่น การ สืบทอด อินเตอร์เฟส และ คลาสนามธรรม จากสามประการนี้ การ สืบทอด น่าจะเป็นสิ่งที่ง่ายที่สุดในการใช้และทำความเข้าใจ จากประสบการณ์ของผมทั้งในฐานะนักเรียนการเขียนโปรแกรมและครู อีกสองคนเกือบจะเป็นปัญหาสำหรับผู้เรียนอย่างสม่ำเสมอ เว้นแต่จะให้ความสนใจเป็นพิเศษเพื่อหลีกเลี่ยงข้อผิดพลาดที่กล่าวถึงก่อนหน้านี้ จากนี้ไป ฉันจะทำให้ดีที่สุดเพื่อทำให้หัวข้อเหล่านี้เรียบง่ายอย่างที่ควรจะเป็น แต่ไม่ง่ายไปกว่านี้
หมายเหตุเกี่ยวกับตัวอย่าง
ฉันจะใช้ตัวอย่างที่นำมาจากแพลตฟอร์มนั้นเพื่อที่ฉันจะได้สอนคุณเกี่ยวกับการสร้างแอปพลิเคชัน GUI ควบคู่ไปกับการแนะนำคุณสมบัติทางภาษาของ Java อย่างไรก็ตาม ฉันจะไม่ลงรายละเอียดมากจนตัวอย่างไม่ควรเข้าใจโดยผู้ที่มีความเข้าใจอย่างคร่าวๆ เกี่ยวกับ Java EE, Swing หรือ JavaFX เป้าหมายสูงสุดของฉันในการอภิปรายหัวข้อเหล่านี้คือการช่วยให้คุณเข้าใจความหมายในบริบทของการแก้ปัญหาในแอปพลิเคชันประเภทใดก็ได้
ข้าพเจ้าขอเตือนท่านผู้อ่านที่รักด้วยว่า บางครั้งอาจดูเหมือนว่าข้าพเจ้าใช้ปรัชญาและอวดดีโดยไม่จำเป็นเกี่ยวกับคำเฉพาะเจาะจงและคำจำกัดความของคำเหล่านั้น เหตุผลก็คือต้องมีรากฐานทางปรัชญาที่ลึกซึ้งอย่างแท้จริงเพื่อทำความเข้าใจความแตกต่างระหว่างสิ่งที่เป็น รูปธรรม (ของจริง) กับบางสิ่งที่เป็น นามธรรม (มีรายละเอียดน้อยกว่าของจริง) ความเข้าใจนี้ใช้ได้กับหลายสิ่งหลายอย่างนอกสาขาการคำนวณ แต่มีความสำคัญสูงเป็นพิเศษสำหรับนักพัฒนาซอฟต์แวร์ทุกรายที่จะเข้าใจธรรมชาติของสิ่งที่ เป็นนามธรรม ไม่ว่าในกรณีใด ถ้าคำพูดของฉันทำให้คุณผิดหวัง ตัวอย่างในโค้ดก็หวังว่าจะไม่เป็นเช่นนั้น
การสืบทอดและการดำเนินการ
เมื่อพูดถึงการสร้างแอปพลิเคชันด้วยอินเทอร์เฟซผู้ใช้แบบกราฟิก (GUI) การ สืบทอด อาจเป็นกลไกที่สำคัญที่สุดในการทำให้สามารถสร้างแอปพลิเคชันได้อย่างรวดเร็ว
แม้ว่าจะมีประโยชน์ที่เข้าใจน้อยกว่าในการใช้ มรดก ที่จะกล่าวถึงในภายหลัง ประโยชน์หลักคือการแบ่งปันการใช้งานระหว่างชั้นเรียน คำว่า "การดำเนินการ" นี้ อย่างน้อยก็เพื่อจุดประสงค์ของบทความนี้ มีความหมายที่ชัดเจน เพื่อให้คำจำกัดความทั่วไปของคำในภาษาอังกฤษ ฉันอาจพูดได้ว่า การนำ บางสิ่งไปใช้จริง คือการ ทำให้มันเป็นจริง
เพื่อให้คำจำกัดความทางเทคนิคเฉพาะสำหรับการพัฒนาซอฟต์แวร์ ข้าพเจ้าอาจกล่าวได้ว่า การนำ ซอฟต์แวร์ไปใช้ คือการเขียนโค้ดที่ เป็นรูปธรรม ซึ่งตรงตามข้อกำหนดของซอฟต์แวร์ดังกล่าว ตัวอย่างเช่น สมมติว่าฉันกำลังเขียน วิธี sum : private double sum(double first, double second){
private double sum(double first, double second){ //TODO: implement }
ข้อมูลโค้ดด้านบนนี้ แม้ว่าฉันจะเขียนถึงประเภทการส่งคืน ( double
) และ การประกาศเมธอด ซึ่งระบุอาร์กิวเมนต์ ( first, second
) และชื่อที่สามารถเรียก เมธอด ดังกล่าว ( sum
) ได้ ไม่ได้ ดำเนินการ ในการนำไป ใช้ เราต้องกรอกข้อมูล เมธอด ดังนี้:
private double sum(double first, double second){ return first + second; }
โดยปกติ ตัวอย่างแรกจะไม่คอมไพล์ แต่เราจะเห็นในชั่วขณะว่า อินเทอร์เฟซ เป็นวิธีที่เราสามารถ เขียน ฟังก์ชันที่ไม่ได้ใช้งานประเภทนี้ได้โดยไม่มีข้อผิดพลาด
มรดกใน Java
สมมุติว่า หากคุณกำลังอ่านบทความนี้ คุณได้ใช้คีย์เวิร์ด extends
Java อย่างน้อยหนึ่งครั้ง กลไกของคีย์เวิร์ดนี้เรียบง่ายและมักอธิบายโดยใช้ตัวอย่างที่เกี่ยวข้องกับสัตว์หรือรูปทรงเรขาคณิตประเภทต่างๆ Dog
และ Cat
ขยายพันธุ์ Animal
เป็นต้น ฉันจะถือว่าฉันไม่จำเป็นต้องอธิบายทฤษฎีประเภทพื้นฐานให้คุณฟัง ดังนั้นให้เราเข้าไปที่ประโยชน์หลักของการ สืบทอด ใน Java ผ่านคีย์เวิร์ด extends
การสร้างแอปพลิเคชัน “Hello World” บนคอนโซลใน Java นั้นง่ายมาก สมมติว่าคุณมี Java Compiler ( javac ) และสภาพแวดล้อมรันไทม์ ( jre ) คุณสามารถเขียนคลาสที่มีฟังก์ชัน หลัก ดังนี้:
public class JavaApp{ public static void main(String []args){ System.out.println("Hello World"); } }
การสร้างแอปพลิเคชัน GUI ใน Java บนเกือบทุกแพลตฟอร์มหลัก (Android, Enterprise/Web, Desktop) ด้วยความช่วยเหลือเล็กน้อยจาก IDE เพื่อสร้างโครงร่าง/โค้ด Boilerplate ของแอปใหม่ ก็ค่อนข้างง่ายเช่นกัน extends
คำสำคัญ
สมมติว่าเรามีเค้าโครง XML ที่เรียกว่า activity_main.xml
(โดยทั่วไปเราจะสร้างส่วนต่อประสานผู้ใช้อย่างเปิดเผยใน Android ผ่านไฟล์ Layout) ที่มี TextView
(เช่นป้ายข้อความ) ที่เรียกว่า tvDisplay
:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:andro android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" /> </FrameLayout>
นอกจากนี้ สมมติว่าเราต้องการให้ tvDisplay
พูดว่า "สวัสดีชาวโลก!" ในการทำเช่นนั้น เราเพียงแค่ต้องเขียนคลาสที่ใช้คีย์เวิร์ด extends
เพื่อสืบทอดจากคลาส Activity
:
import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((TextView)findViewById(R.id.tvDisplay)).setText("Hello World"); }
ผลของการสืบทอดการใช้งานคลาส Activity
นั้นสามารถชื่นชมได้ดีที่สุดโดยดูอย่างรวดเร็วที่ซอร์สโค้ด ฉันสงสัยอย่างยิ่งว่า Android จะกลายเป็นแพลตฟอร์มมือถือที่โดดเด่น หากจำเป็นต้อง ใช้ แม้เพียงส่วนเล็ก ๆ ของ 8000+ บรรทัดที่จำเป็นในการโต้ตอบกับระบบ เพียงเพื่อสร้างหน้าต่างง่ายๆ พร้อมข้อความบางส่วน การ สืบทอด คือสิ่งที่ช่วยให้เราไม่ต้องสร้าง กรอบงาน Android ใหม่ หรือแพลตฟอร์มใดก็ตามที่คุณใช้งานอยู่ตั้งแต่เริ่มต้น
มรดกสามารถใช้สำหรับนามธรรม
ตราบเท่าที่สามารถใช้เพื่อแบ่งปันการใช้งานข้ามคลาส การ สืบทอด นั้นค่อนข้างเข้าใจง่าย อย่างไรก็ตาม มีอีกวิธีหนึ่งที่สำคัญที่สามารถใช้การ สืบทอด ได้ ซึ่งเกี่ยวข้องกับแนวคิดเกี่ยวกับ อินเทอร์เฟซ และ คลาสนามธรรม ซึ่งเราจะพูดถึงในเร็วๆ นี้
หากคุณได้โปรด สมมติต่อไปในขณะที่สิ่งที่เป็นนามธรรมซึ่งใช้ในความหมายทั่วไปมากที่สุด เป็นตัวแทนที่มีรายละเอียดน้อยกว่าของสิ่งของ แทนที่จะพิจารณาว่าด้วยคำจำกัดความทางปรัชญาที่ยืดยาว ฉันจะพยายามชี้ให้เห็นว่าสิ่งที่ เป็นนามธรรม ทำงานอย่างไรในชีวิตประจำวัน และหลังจากนั้นไม่นานก็อภิปรายอย่างชัดเจนในแง่ของการพัฒนาซอฟต์แวร์
สมมติว่าคุณกำลังเดินทางไปออสเตรเลีย และคุณทราบว่าภูมิภาคที่คุณกำลังเยี่ยมชมนั้นมีงูไทปันอาศัยอยู่หนาแน่นสูง (เห็นได้ชัดว่ามีพิษมาก) คุณตัดสินใจปรึกษา Wikipedia เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับการดูรูปภาพและข้อมูลอื่นๆ การทำเช่นนี้ทำให้คุณได้ตระหนักถึงงูชนิดใดชนิดหนึ่งที่คุณไม่เคยเห็นมาก่อน
สิ่งที่เป็นนามธรรม แนวคิด แบบจำลอง หรืออะไรก็ตามที่คุณต้องการจะเรียกว่าเป็นการนำเสนอที่มีรายละเอียดน้อยกว่า สิ่งสำคัญคือต้องมีรายละเอียดน้อยกว่าของจริงเพราะงูจริงสามารถกัดคุณได้ รูปภาพในหน้า Wikipedia มักไม่ทำ สิ่งที่เป็น นามธรรม ก็มีความสำคัญเช่นกัน เนื่องจากทั้งคอมพิวเตอร์และสมองของมนุษย์มีความสามารถจำกัดในการจัดเก็บ สื่อสาร และประมวลผลข้อมูล การมีรายละเอียดเพียงพอที่จะใช้ข้อมูลนี้ในทางปฏิบัติ โดยไม่ต้องใช้พื้นที่ในหน่วยความจำมากเกินไป เป็นสิ่งที่ทำให้คอมพิวเตอร์และสมองของมนุษย์สามารถแก้ปัญหาได้เหมือนกัน
เพื่อเชื่อมโยงสิ่งนี้กลับเป็น มรดก หัวข้อหลักทั้งสามที่ฉันกำลังพูดถึงในที่นี้สามารถใช้เป็น นามธรรม หรือกลไกของสิ่งที่ เป็นนามธรรม ได้ สมมติว่าในไฟล์เลย์เอาต์ของแอพ “Hello World” เราตัดสินใจเพิ่ม ImageView
, Button
และ ImageButton
:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andro android:layout_width="match_parent" android:layout_height="match_parent"> <Button android: android:layout_width="wrap_content" android:layout_height="wrap_content"/> <ImageButton android: android:layout_width="wrap_content" android:layout_height="wrap_content"/> <ImageView android: android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout>
สมมติว่ากิจกรรมของเราใช้ View.OnClickListener
เพื่อจัดการกับการคลิก:
public class MainActivity extends Activity implements View.OnClickListener { private Button b; private ImageButton ib; private ImageView iv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //... b = findViewById(R.id.imvDisplay).setOnClickListener(this); ib = findViewById(R.id.btnDisplay).setOnClickListener(this); iv = findViewById(R.id.imbDisplay).setOnClickListener(this); } @Override public void onClick(View view) { final int id = view.getId(); //handle click based on id... } }
หลักการสำคัญที่นี่คือ Button
, ImageButton
และ ImageView
สืบทอดจากคลาส View
ผลลัพธ์คือฟังก์ชัน onClick
นี้สามารถรับเหตุการณ์การคลิกจากองค์ประกอบ UI ที่แตกต่างกัน (แม้ว่าจะเกี่ยวข้องตามลำดับชั้น) โดยอ้างอิงว่าเป็นคลาสพาเรนต์ที่มีรายละเอียดน้อยกว่า สะดวกกว่าการเขียน วิธี จัดการวิดเจ็ตทุกประเภทบน แพลตฟอร์ม Android อย่างชัดเจน (ไม่ต้องพูดถึงวิดเจ็ตที่กำหนดเอง)
อินเทอร์เฟซและนามธรรม
คุณอาจพบว่าตัวอย่างโค้ดก่อนหน้านี้ไม่น่าสนใจ แม้ว่าคุณจะเข้าใจว่าทำไมฉันถึงเลือกมัน ความสามารถในการแชร์ การใช้งาน ข้ามลำดับชั้นของคลาสนั้นมีประโยชน์อย่างเหลือเชื่อ และฉันจะยืนยันว่าเป็นยูทิลิตี้หลักของการ สืบทอด สำหรับการอนุญาตให้เราปฏิบัติต่อชุดของคลาสที่มีคลาสพาเรนต์ร่วมกันใน ประเภท ที่เท่ากัน (เช่น คลาส พาเรน ต์ ) คุณลักษณะของการ สืบทอด นั้นมีการใช้งานที่จำกัด
ฉันกำลังพูดถึงข้อกำหนดของคลาสย่อยให้อยู่ในลำดับชั้นของคลาสเดียวกันเพื่อที่จะอ้างอิงผ่านหรือเรียกว่าคลาสพาเรนต์ กล่าวอีกนัยหนึ่ง การ สืบทอด เป็นกลไกที่เข้มงวดมากสำหรับสิ่งที่ เป็นนามธรรม อันที่จริง หากฉันคิดว่าสิ่งที่ เป็นนามธรรม นั้นเป็นสเปกตรัมที่เคลื่อนที่ระหว่างรายละเอียด (หรือข้อมูล) ระดับต่างๆ ฉันอาจกล่าวได้ว่าการ สืบทอด เป็นกลไก ที่เป็นนามธรรมน้อยที่สุด สำหรับการ สร้างนามธรรม ใน Java
ก่อนที่ฉันจะพูดถึง อินเทอร์เฟซ ฉันอยากจะพูดถึงว่าใน Java 8 มีการเพิ่มคุณสมบัติสองอย่างที่เรียกว่า Default Methods และ Static Methods ลงใน อินเทอร์เฟซ ฉันจะพูดถึงพวกเขาในที่สุด แต่สำหรับตอนนี้ฉันต้องการให้เราแสร้งทำเป็นว่าพวกเขาไม่มีอยู่จริง เพื่อให้ง่ายต่อการอธิบายวัตถุประสงค์หลักของการใช้ อินเทอร์เฟซ ซึ่งในตอนแรกยังคงเป็น กลไกที่เป็นนามธรรมที่สุดสำหรับสิ่งที่เป็นนามธรรมใน Java
รายละเอียดน้อยหมายถึงอิสระมากขึ้น
ในส่วนเกี่ยวกับ มรดก ฉันได้ให้คำจำกัดความของคำว่า การนำไปใช้ ซึ่งมีความหมายตรงกันข้ามกับคำศัพท์อื่นที่เราจะพูดถึงในตอนนี้ เพื่อความชัดเจน ฉันไม่สนใจคำศัพท์เอง หรือว่าคุณเห็นด้วยกับการใช้คำเหล่านั้นหรือไม่ เฉพาะที่คุณเข้าใจสิ่งที่พวกเขาชี้ทางแนวคิด
ในขณะที่การ สืบทอด เป็นเครื่องมือหลักในการแบ่งปัน การใช้งาน ข้ามชุดของคลาส เราอาจกล่าวได้ว่า อินเทอร์เฟซ เป็นกลไกหลักในการแบ่งปัน พฤติกรรม ข้ามชุดของคลาส พฤติกรรม ที่ใช้ในความหมายนี้เป็นเพียงคำที่ไม่ใช่เชิงเทคนิคสำหรับ วิธีการเชิงนามธรรม เท่านั้น วิธีนามธรรม เป็น วิธีการ ที่ในความเป็นจริงไม่สามารถมี เนื้อหาวิธีการ :
public interface OnClickListener { void onClick(View v); }
ปฏิกิริยาตามธรรมชาติสำหรับฉันและบุคคลจำนวนหนึ่งที่ฉันสอนหลังจากดู อินเทอร์เฟซ ครั้งแรก คือการสงสัยว่ายูทิลิตี้ของการแบ่งปันเฉพาะ ประเภทส่งคืน ชื่อเมธอด และ รายการพารามิเตอร์คือ อะไร ภายนอก ดูเหมือนวิธีที่ดีในการสร้างงานเพิ่มเติมสำหรับตัวคุณเอง หรือใครก็ตามที่อาจกำลังเขียนคลาสที่ implements
อินเทอร์เฟซ คำตอบคือ อินเทอร์เฟซ นั้นสมบูรณ์แบบสำหรับสถานการณ์ที่คุณต้องการให้ชุดของคลาส ทำงาน ในลักษณะเดียวกัน (กล่าวคือ มี วิธีการนามธรรม สาธารณะเหมือนกัน) แต่คุณคาดหวังให้พวกเขา นำ พฤติกรรม นั้นไปใช้ในรูปแบบที่ต่างกัน
เพื่อยกตัวอย่างที่เรียบง่ายแต่มีความเกี่ยวข้อง แพลตฟอร์ม Android มีสองคลาสซึ่งส่วนใหญ่อยู่ในธุรกิจของการสร้างและจัดการส่วนหนึ่งของอินเทอร์เฟซผู้ใช้: Activity
และ Fragment
ย่อย ตามนั้นคลาสเหล่านี้มักจะมีข้อกำหนดในการฟังเหตุการณ์ที่ปรากฏขึ้นเมื่อมีการคลิกวิดเจ็ต (หรือโต้ตอบกับผู้ใช้) เพื่อประโยชน์ในการโต้แย้ง ให้เราใช้เวลาสักครู่เพื่อชื่นชมว่าทำไม มรดก จึงแทบไม่เคยแก้ปัญหาดังกล่าวได้:
public class OnClickManager { public void onClick(View view){ //Wait a minute... Activities and Fragments almost never //handle click events exactly the same way... } }
ไม่เพียงแต่การทำให้ กิจกรรม และส่วนต่างๆ ของเราสืบทอดมาจาก OnClickManager ทำให้ไม่สามารถจัดการกิจกรรมในลักษณะที่แตกต่างออกไปได้ แต่ OnClickManager
คือเราไม่สามารถทำอย่างนั้นได้หากต้องการ ทั้ง Activity และ Fragment ได้ขยายพาเรน ต์คลาส แล้ว และ Java ไม่อนุญาตให้พาเรนต์คลาสหลาย คลาส ปัญหาของเราคือเราต้องการให้ชุดของคลาส ทำงาน ในลักษณะเดียวกัน แต่ เราต้อง มีความยืดหยุ่นในวิธีที่คลาส ใช้ พฤติกรรม นั้น สิ่งนี้นำเรากลับไปที่ตัวอย่างก่อนหน้าของ View.OnClickListener
:
public interface OnClickListener { void onClick(View v); }
นี่คือซอร์สโค้ดจริง (ซึ่งซ้อนอยู่ในคลาส View
) และสองสามบรรทัดเหล่านี้ช่วยให้เราตรวจสอบ พฤติกรรม ที่สอดคล้องกันในวิดเจ็ตต่างๆ ( Views ) และตัวควบคุม UI ( Activities, Fragments ฯลฯ )
สิ่งที่เป็นนามธรรมส่งเสริมการมีเพศสัมพันธ์แบบหลวม
ฉันหวังว่าจะได้ตอบคำถามทั่วไปเกี่ยวกับสาเหตุที่อินเทอร์เฟซมีอยู่ใน Java; ท่ามกลางภาษาอื่นๆ อีกมากมาย จากมุมมองหนึ่ง มันเป็นเพียงวิธีการแบ่งปันรหัสระหว่างชั้นเรียน แต่มีรายละเอียดน้อยกว่าโดยเจตนาเพื่อให้สามารถ นำไปใช้งาน ที่แตกต่างกันได้ แต่เช่นเดียวกับการ สืบทอด สามารถใช้เป็นกลไกในการแบ่งปันรหัสและสิ่งที่ เป็นนามธรรม (แม้ว่าจะมีข้อจำกัดในลำดับชั้นของคลาส) อินเทอร์เฟซ ก็ให้กลไกที่ยืดหยุ่นกว่าสำหรับ นามธรรม
ในส่วนก่อนหน้าของบทความนี้ ฉันได้แนะนำหัวข้อของ การมีเพศสัมพันธ์หลวม/แน่น โดยการเปรียบเทียบความแตกต่างระหว่างการใช้ ตะปู และ สกรู เพื่อสร้างโครงสร้างบางประเภท สรุป แนวคิดพื้นฐานคือคุณจะต้องการใช้ สกรู ในสถานการณ์ที่การเปลี่ยนแปลงโครงสร้างที่มีอยู่ (ซึ่งอาจเป็นผลมาจากการแก้ไขข้อผิดพลาด การเปลี่ยนแปลงการออกแบบ และอื่นๆ) มีแนวโน้มที่จะเกิดขึ้น ตะปู ใช้ได้ดีเมื่อคุณต้องการยึดส่วนต่างๆ ของโครงสร้างเข้าด้วยกัน และไม่ต้องกังวลว่าจะแยกออกจากกันในอนาคตอันใกล้
ตะปู และ ตะปู มีไว้เพื่อให้คล้ายคลึงกับการอ้างอิงที่เป็น รูปธรรม และ นามธรรม (ใช้คำว่าการ พึ่งพาอาศัย ด้วย) ระหว่างชั้นเรียน เพื่อไม่ให้เกิดความสับสน ตัวอย่างต่อไปนี้จะแสดงให้เห็นว่าฉันหมายถึงอะไร:
class Client { private Validator validator; private INetworkAdapter networkAdapter; void sendNetworkRequest(String input){ if (validator.validateInput(input)) { try { networkAdapter.sendRequest(input); } catch (IOException e){ //handle exception } } } } class Validator { //...validation logic boolean validateInput(String input){ boolean isValid = true; //...change isValid to false based on validation logic return isValid; } } interface INetworkAdapter { //... void sendRequest(String input) throws IOException; }
ที่นี่ เรามีคลาสที่เรียกว่า Client
ซึ่งมี การอ้างอิง สองประเภท ขอให้สังเกตว่า สมมติว่า Client
ต์ไม่มีส่วนเกี่ยวข้องกับการสร้าง ข้อมูลอ้างอิง (ซึ่งไม่ควรจริงๆ) มันจะแยกออกจากรายละเอียดการใช้งานของอะแดปเตอร์เครือข่ายเฉพาะใดๆ
มีนัยสำคัญบางประการของการ มีเพศสัมพันธ์แบบหลวม ๆ นี้ สำหรับผู้เริ่มต้น ฉันสามารถสร้าง Client
ต์โดยแยก การใช้งาน INetworkAdapter
แบบแยกส่วนโดยสิ้นเชิง ลองนึกภาพว่าคุณกำลังทำงานในทีมนักพัฒนาสองคน หนึ่งเพื่อสร้างส่วนหน้า หนึ่งเพื่อสร้างส่วนหลัง ตราบใดที่นักพัฒนาทั้งสองยังคงทราบถึง อินเทอร์เฟซ ที่รวมคลาสที่เกี่ยวข้องเข้าด้วยกัน พวกเขาสามารถทำงานต่อไปได้อย่างแท้จริงโดยอิสระจากกัน
ประการที่สอง จะเกิดอะไรขึ้นถ้าฉันบอกคุณว่านักพัฒนาทั้งสองสามารถยืนยันได้ว่า การใช้งาน ที่เกี่ยวข้องกันนั้นทำงานอย่างถูกต้อง โดยไม่ขึ้นกับความคืบหน้าของกันและกัน มันง่ายมากด้วยอินเทอร์เฟซ เพียงสร้าง Test Double ซึ่ง implements
อินเทอร์เฟซ ที่เหมาะสม:
class FakeNetworkAdapter implements INetworkAdapter { public boolean throwError = false; @Override public void sendRequest(String input) throws IOException { if (throwError) throw new IOException("Test Exception"); } }
โดยหลักการแล้ว สิ่งที่สามารถสังเกตได้ก็คือการทำงานกับ การอ้างอิงที่เป็นนามธรรม เปิดประตูสู่ความเป็นโมดูล การทดสอบ และรูปแบบการออกแบบที่ทรงพลังมากขึ้น เช่น Facade Pattern , Observer Pattern และอื่นๆ พวกเขายังช่วยให้นักพัฒนาค้นหาความสมดุลที่มีความสุขในการออกแบบส่วนต่างๆ ของระบบโดยพิจารณาจาก พฤติกรรม ( Program To An Interface ) โดยไม่ต้องกังวลใจในรายละเอียด การใช้งาน
จุดสุดท้ายเกี่ยวกับนามธรรม
นามธรรม ไม่ได้มีอยู่ในลักษณะเดียวกับสิ่งที่เป็น รูปธรรม สิ่งนี้สะท้อนให้เห็นในภาษาการเขียนโปรแกรม Java โดยข้อเท็จจริงที่ว่า คลาสนามธรรม และ อินเทอร์เฟซ อาจไม่สามารถสร้างอินสแตนซ์ได้
ตัวอย่างเช่น สิ่งนี้จะไม่คอมไพล์อย่างแน่นอน:
public class Main extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { //ERROR x2: Foo f = new Foo(); Bar b = new Bar() } private abstract class Foo{} private interface Bar{} }
อันที่จริง แนวคิดในการคาดหวังว่า อินเทอร์เฟซ ที่ไม่ได้ใช้งานหรือ คลาสนามธรรม จะทำงานที่รันไทม์นั้นสมเหตุสมผลพอๆ กับการคาดหวังให้ชุดเครื่องแบบของ UPS ลอยอยู่รอบๆ การส่งพัสดุภัณฑ์ สิ่งที่เป็นรูปธรรมต้องอยู่เบื้องหลังสิ่งที่ เป็นนามธรรม เพื่อให้เป็นประโยชน์ แม้ว่าคลาสที่เรียกไม่จำเป็นต้องรู้ว่าสิ่งที่อยู่เบื้องหลัง การอ้างอิงที่เป็นนามธรรม จริงๆ
คลาสนามธรรม: นำทุกอย่างมารวมกัน
หากคุณมาไกลถึงขนาดนี้ ฉันยินดีที่จะบอกคุณว่าฉันไม่มีความหมายทางปรัชญาหรือวลีศัพท์แสงที่จะแปลอีกต่อไป พูดง่ายๆ คลาสนามธรรม เป็นกลไกสำหรับ การนำไปใช้ และ พฤติกรรม ร่วมกันระหว่างชุดของคลาส ตอนนี้ฉันจะยอมรับทันทีว่าฉันไม่ได้ใช้ คลาสนามธรรม บ่อยนัก ถึงอย่างนั้น ความหวังของฉันก็คือว่าเมื่อสิ้นสุดส่วนนี้ คุณจะรู้แน่ชัดว่าเมื่อใดที่พวกเขาได้รับเรียก
กรณีศึกษาบันทึกการออกกำลังกาย
ประมาณหนึ่งปีในการสร้างแอพ Android ใน Java ฉันกำลังสร้างแอพ Android ตัวแรกของฉันใหม่ตั้งแต่ต้น เวอร์ชันแรกเป็นโค้ดจำนวนมากที่คุณคาดหวังจากนักพัฒนาที่เรียนรู้ด้วยตนเองพร้อมคำแนะนำเพียงเล็กน้อย เมื่อถึงเวลาที่ฉันต้องการเพิ่มฟังก์ชันการทำงานใหม่ ก็เห็นได้ ชัด ว่าโครงสร้างที่ฉันสร้างขึ้นโดยใช้ ตะปู เพียงอย่างเดียว เป็นไปไม่ได้เลยที่จะรักษาไว้จนฉันต้องสร้างใหม่ทั้งหมด
แอพนี้เป็นบันทึกการออกกำลังกายที่ออกแบบมาเพื่อให้บันทึกการออกกำลังกายของคุณได้ง่าย และความสามารถในการส่งออกข้อมูลของการออกกำลังกายที่ผ่านมาเป็นไฟล์ข้อความหรือรูปภาพ โดยไม่ได้ลงรายละเอียดมากเกินไป ฉันจัดโครงสร้างโมเดลข้อมูลของแอปให้มีออบเจกต์ Workout
ซึ่งประกอบด้วยคอลเล็กชันของออบเจกต์ Exercise
(นอกเหนือจากสาขาอื่นๆ ที่ไม่เกี่ยวข้องกับการสนทนานี้)
ขณะที่ฉันกำลังใช้คุณสมบัตินี้ในการส่งข้อมูลการออกกำลังกายไปยังสื่อที่มองเห็นได้ ฉันตระหนักว่าฉันต้องจัดการกับปัญหา: การออกกำลังกายประเภทต่างๆ จะต้องมีการแสดงผลข้อความประเภทต่างๆ
เพื่อให้แนวคิดคร่าวๆ แก่คุณ ฉันต้องการเปลี่ยนผลลัพธ์ตามประเภทของการออกกำลังกายดังนี้:
- ยกน้ำหนัก: 10 REPS @ 100 LBS
- ดัมเบล: 10 REPS @ 50 LBS x2
- น้ำหนักตัว: 10 REPS @ น้ำหนักตัว
- น้ำหนักตัว +: 10 REPS @ น้ำหนักตัว + 45 LBS
- กำหนดเวลา: 60 SEC @ 100 LBS
ก่อนที่ฉันจะดำเนินการต่อ โปรดทราบว่ามีประเภทอื่นๆ (การออกกำลังกายอาจกลายเป็นเรื่องที่ซับซ้อน) และโค้ดที่ฉันจะแสดงได้รับการตัดแต่งและเปลี่ยนให้พอดีกับบทความ
เพื่อให้สอดคล้องกับคำจำกัดความของฉันก่อนหน้านี้ เป้าหมายของการเขียน คลาสนามธรรม คือ การนำ ทุกอย่างไปใช้ (แม้กระทั่ง สถานะ เช่น ตัวแปร และ ค่าคง ที่) ซึ่งใช้ร่วมกันใน คลาสย่อยทั้งหมดในคลาส นามธรรม จากนั้น สำหรับสิ่งที่เปลี่ยนแปลงใน คลาสย่อย ดังกล่าว ให้สร้าง เมธอด abstract :
abstract class Exercise { private final String type; protected final String name; protected final int[] repetitionsOrTime; protected final double[] weight; protected static final String POUNDS = "LBS"; protected static final String SECONDS = "SEC "; protected static final String REPETITIONS = "REPS "; public Exercise(String type, String name, int[] repetitionsOrTime, double[] weight) { this.type = type; this.name = name; this.repetitionsOrTime = repetitionsOrTime; this.weight = weight; } public String getFormattedOutput(){ StringBuilder sb = new StringBuilder(); sb.append(name); sb.append("\n"); getSetData(sb); sb.append("\n"); return sb.toString(); } /** * Append data appropriately based on Exercise type * @param sb - StringBuilder to Append data to */ protected abstract void getSetData(StringBuilder sb); //...Getters }
ฉันอาจระบุสิ่งที่ชัดเจน แต่ถ้าคุณมีคำถามเกี่ยวกับสิ่งที่ควรหรือไม่ควรนำ ไปใช้ ใน abstract class กุญแจสำคัญคือการดูส่วนใดส่วนหนึ่งของการ ใช้งาน ที่ทำซ้ำในคลาสย่อยทั้งหมด
ตอนนี้เราได้สร้างสิ่งที่พบได้ทั่วไปในแบบฝึกหัดทั้งหมดแล้ว เราสามารถเริ่มสร้างคลาสย่อยที่มีความเชี่ยวชาญเฉพาะสำหรับเอาต์พุตสตริงแต่ละประเภท:
การออกกำลังกายแบบยกน้ำหนัก:
class BarbellExercise extends Exercise { public BarbellExercise(String type, String name, int[] repetitionsOrTime, double[] weight) { super(type, name, repetitionsOrTime, weight); } @Override protected void getSetData(StringBuilder sb) { for (int i = 0; i < repetitionsOrTime.length; i++) { sb.append(repetitionsOrTime[i]); sb.append(" "); sb.append(REPETITIONS); sb.append(" @ "); sb.append(weight[i]); sb.append(POUNDS); sb.append("\n"); } } }
การออกกำลังกายดัมเบลล์:
class DumbbellExercise extends Exercise { private static final String TIMES_TWO = "x2"; public DumbbellExercise(String type, String name, int[] repetitionsOrTime, double[] weight) { super(type, name, repetitionsOrTime, weight); } @Override protected void getSetData(StringBuilder sb) { for (int i = 0; i < repetitionsOrTime.length; i++) { sb.append(repetitionsOrTime[i]); sb.append(" "); sb.append(REPETITIONS); sb.append(" @ "); sb.append(weight[i]); sb.append(POUNDS); sb.append(TIMES_TWO); sb.append("\n"); } } }
การออกกำลังกายน้ำหนักตัว:
class BodyweightExercise extends Exercise { private static final String BODYWEIGHT = "Bodyweight"; public BodyweightExercise(String type, String name, int[] repetitionsOrTime, double[] weight) { super(type, name, repetitionsOrTime, weight); } @Override protected void getSetData(StringBuilder sb) { for (int i = 0; i < repetitionsOrTime.length; i++) { sb.append(repetitionsOrTime[i]); sb.append(" "); sb.append(REPETITIONS); sb.append(" @ "); sb.append(BODYWEIGHT); sb.append("\n"); } } }
ฉันแน่ใจว่าผู้อ่านที่ชาญฉลาดบางคนจะพบสิ่งต่าง ๆ ที่สามารถ แยกออกมา ในลักษณะที่มีประสิทธิภาพมากขึ้น แต่จุดประสงค์ของตัวอย่างนี้ (ซึ่งได้รับการทำให้ง่ายขึ้นจากแหล่งที่มาดั้งเดิม) คือการแสดงวิธีการทั่วไป แน่นอน บทความเกี่ยวกับการเขียนโปรแกรมจะไม่สมบูรณ์หากไม่มีสิ่งที่สามารถดำเนินการได้ มีคอมไพเลอร์ Java ออนไลน์หลายตัวที่คุณอาจใช้เพื่อเรียกใช้โค้ดนี้ หากคุณต้องการทดสอบ (ยกเว้นกรณีที่คุณมี IDE อยู่แล้ว):
public class Main { public static void main(String[] args) { //Note: I actually used another nested class called a "Set" instead of an Array //to represent each Set of an Exercise. int[] reps = {10, 10, 8}; double[] weight = {70.0, 70.0, 70.0}; Exercise e1 = new BarbellExercise( "Barbell", "Barbell Bench Press", reps, weight ); Exercise e2 = new DumbbellExercise( "Dumbbell", "Dumbbell Bench Press", reps, weight ); Exercise e3 = new BodyweightExercise( "Bodyweight", "Push Up", reps, weight ); System.out.println( e1.getFormattedOutput() + e2.getFormattedOutput() + e3.getFormattedOutput() ); } }
Executing this toy application yields the following output: Barbell Bench Press
10 REPS @ 70.0LBS 10 REPS @ 70.0LBS 8 REPS @ 70.0LBS Dumbbell Bench Press 10 REPS @ 70.0LBSx2 10 REPS @ 70.0LBSx2 8 REPS @ 70.0LBSx2 Push Up 10 REPS @ Bodyweight 10 REPS @ Bodyweight 8 REPS @ Bodyweight
Further Considerations
Earlier, I mentioned that there are two features of Java interfaces (as of Java 8) which are decidedly geared towards sharing implementation , as opposed to behavior . These features are known as Default Methods and Static Methods .
I have decided not to go into detail on these features for the reason that they are most typically used in mature and/or large code bases where a given interface has many inheritors. Despite the fact that this is meant to be an introductory article, and I still encourage you to take a look at these features eventually, even though I am confident that you will not need to worry about them just yet.
I would also like to mention that there are other ways to share implementation across a set of classes (or even static methods ) in a Java application that does not require inheritance or abstraction at all. For example, suppose you have some implementation which you expect to use in a variety of different classes, but does not necessarily make sense to share via inheritance . A common pattern in Java is to write what is known as a Utility class, which is a simple class
containing the requisite implementation in a static method :
public class TimeConverterUtil { /** * Accepts an hour (0-23) and minute (0-59), then attempts to format them into an appropriate * format such as 12, 30 -> 12:30 pm */ public static String convertTime (int hour, int minute){ String unformattedTime = Integer.toString(hour) + ":" + Integer.toString(minute); DateFormat f1 = new SimpleDateFormat("HH:mm"); Date d = null; try { d = f1.parse(unformattedTime); } catch (ParseException e) { e.printStackTrace(); } DateFormat f2 = new SimpleDateFormat("h:mm a"); return f2.format(d).toLowerCase(); } }
Using this static method in an external class (or another static method ) looks like this:
public class Main { public static void main(String[] args){ //... String time = TimeConverterUtil.convertTime(12, 30); //... } }
แผ่นโกง
We have covered a lot of ground in this article, so I would like to spend a moment summarizing the three main mechanisms based on what problems they solve. Since you should possess a sufficient understanding of the terms and ideas I have either introduced or redefined for the purposes of this article, I will keep the summaries brief.
I Want A Set Of Child Classes To Share Implementation
Classic inheritance , which requires a child class to inherit from a parent class , is a very simple mechanism for sharing implementation across a set of classes. An easy way to decide if some implementation should be pulled into a parent class , is to see whether it is repeated in a number of different classes line for line. The acronym DRY ( Don't Repeat Yourself ) is a good mnemonic device to watch out for this situation.
While coupling child classes together with a common parent class can present some limitations, a side benefit is that they can all be referenced as the parent class , which provides a limited degree of abstraction .
I Want A Set Of Classes To Share Behavior
Sometimes, you want a set of classes to be capable of possessing certain abstract methods (referred to as behavior ), but you do not expect the implementation of that behavior to be repeated across inheritors.
By definition, Java interfaces may not contain any implementation (except for Default and Static Methods ), but any class which implements an interface , must supply an implementation for all abstract methods, otherwise, the code will not compile. This provides a healthy measure of flexibility and restriction on what is actually shared and does not require the inheritors to be of the same class hierarchy .
I Want A Set Of Child Classes To Share Behavior And Implementation
Although I do not find myself using abstract classes all over the place, they are perfect for situations when you require a mechanism for sharing both behavior and implementation across a set of classes. Anything which will be repeated across inheritors may be implemented directly in the abstract class
, and anything which requires flexibility may be specified as an abstract method .