อาร์กิวเมนต์ "น่ากลัวสำหรับหน่วยความจำ" ผิดทั้งหมด แต่เป็น"การปฏิบัติที่ไม่ดี" อย่างเป็นกลาง เมื่อคุณรับช่วงจากคลาสคุณไม่เพียง แต่สืบทอดฟิลด์และเมธอดที่คุณสนใจ แต่คุณจะสืบทอดทุกสิ่งแทน ทุกวิธีที่ประกาศถึงแม้ว่ามันจะไม่เป็นประโยชน์สำหรับคุณ และที่สำคัญที่สุดคุณยังต้องสืบทอดสัญญาและการรับประกันทั้งหมดที่ชั้นเรียนมอบให้
ตัวย่อ SOLID ให้การวิเคราะห์พฤติกรรมสำหรับการออกแบบเชิงวัตถุที่ดี ที่นี่I nterface Segregation Principle (ISP) และL iskov Substitution Pricinple (LSP) มีบางอย่างที่จะพูด
ISP บอกให้เรารักษาอินเตอร์เฟสให้เล็กที่สุดเท่าที่จะทำได้ แต่ด้วยการสืบทอดArrayList
คุณจะได้รับวิธีการมากมาย มันมีความหมายกับget()
, remove()
, set()
(แทนที่) หรือadd()
(แทรก) โหนดเด็กที่ดัชนีโดยเฉพาะ? เป็นเรื่องที่สมเหตุสมผลensureCapacity()
หรือไม่สำหรับรายการที่อยู่ภายใน? sort()
โหนดหมายถึงอะไร? ผู้ใช้ในชั้นเรียนของคุณควรได้รับจริงsubList()
หรือ เนื่องจากคุณไม่สามารถซ่อนเมธอดที่คุณไม่ต้องการได้ทางออกเดียวคือให้ ArrayList เป็นตัวแปรสมาชิกและส่งต่อเมธอดทั้งหมดที่คุณต้องการ:
private final ArrayList<Node> children = new ArrayList();
public void add(Node child) { children.add(child); }
public Iterator<Node> iterator() { return children.iterator(); }
หากคุณต้องการวิธีการทั้งหมดที่คุณเห็นในเอกสารเราสามารถไปยัง LSP LSP บอกเราว่าเราจะต้องสามารถใช้คลาสย่อยได้ในทุกที่ที่ผู้ปกครองต้องการ หากฟังก์ชั่นรับArrayList
พารามิเตอร์เป็นและเราส่งผ่านNode
แทนไม่มีอะไรควรจะเปลี่ยน
ความเข้ากันได้ของคลาสย่อยเริ่มต้นด้วยสิ่งง่าย ๆ เช่นลายเซ็นประเภท เมื่อคุณแทนที่เมธอดคุณจะไม่สามารถทำให้พารามิเตอร์เข้มงวดมากขึ้นเนื่องจากอาจแยกการใช้ที่ถูกกฎหมายกับคลาสพาเรนต์ แต่นั่นเป็นสิ่งที่คอมไพเลอร์ตรวจสอบเราใน Java
แต่ LSP ทำงานได้ลึกกว่านี้มาก: เราต้องรักษาความเข้ากันได้กับทุกสิ่งที่สัญญาไว้ในเอกสารของคลาสแม่และอินเทอร์เฟซทั้งหมด ในคำตอบของพวกเขาลินน์พบกรณีหนึ่งกรณีที่List
อินเทอร์เฟซ (ซึ่งคุณสืบทอดมาArrayList
) รับประกันว่าวิธีการequals()
และhashCode()
วิธีการทำงานอย่างไร สำหรับhashCode()
คุณจะได้รับอัลกอริทึมเฉพาะที่ต้องดำเนินการอย่างแน่นอน สมมติว่าคุณเขียนสิ่งนี้Node
:
public class Node extends ArrayList<Node> {
public final int value;
public Node(int value, Node... children) {
this.value = Value;
for (Node child : children)
add(child);
}
...
}
สิ่งนี้ต้องการให้สิ่งที่value
ไม่สามารถมีส่วนร่วมhashCode()
และไม่สามารถมีอิทธิพลequals()
ได้ List
อินเตอร์เฟซ - ซึ่งคุณสัญญาว่าจะให้เกียรติโดยการสืบทอดจากมัน - ต้องการnew Node(0).equals(new Node(123))
ที่จะเป็นจริง
เพราะการสืบทอดจากชั้นเรียนจะทำให้มันง่ายเกินไปที่จะตั้งใจผิดสัญญาที่ผู้ปกครองชั้นเรียนทำและเพราะมันมักจะหมายความว่าวิธีการมากกว่าที่คุณตั้งใจจะชี้ให้เห็นโดยทั่วไปว่าคุณต้องการองค์ประกอบมรดก หากคุณต้องสืบทอดบางสิ่งขอแนะนำให้สืบทอดส่วนต่อประสานเท่านั้น หากคุณต้องการนำพฤติกรรมของคลาสเฉพาะกลับมาใช้ใหม่คุณสามารถทำให้มันเป็นวัตถุแยกต่างหากในตัวแปรอินสแตนซ์ซึ่งจะทำให้สัญญาและข้อกำหนดทั้งหมดไม่ได้บังคับกับคุณ
บางครั้งภาษาธรรมชาติของเราแสดงให้เห็นถึงความสัมพันธ์ของการสืบทอด: รถยนต์เป็นพาหนะ รถจักรยานยนต์เป็นยานพาหนะ ฉันควรกำหนดคลาสCar
และMotorcycle
สืบทอดมาจากVehicle
คลาสหรือไม่ การออกแบบเชิงวัตถุไม่ได้เกี่ยวกับการสะท้อนโลกแห่งความเป็นจริงในรหัสของเรา เราไม่สามารถเข้ารหัส taxonomies มากมายของโลกแห่งความจริงในซอร์สโค้ดของเราได้อย่างง่ายดาย
ตัวอย่างหนึ่งคือปัญหาการสร้างแบบจำลองพนักงาน - หัวหน้า เรามีหลายรายการPerson
แต่ละแห่งมีชื่อและที่อยู่ Employee
เป็นและมีPerson
ยังเป็น ดังนั้นฉันควรสร้างคลาสที่สืบทอดและ? ตอนนี้ฉันมีปัญหา: เจ้านายก็เป็นพนักงานและก็มีหัวหน้าอีกคนหนึ่ง ดังนั้นจึงดูเหมือนว่าควรขยาย แต่เป็นแต่ไม่ได้เป็น? กราฟการสืบทอดประเภทใดจะแตกลงที่นี่Boss
Boss
Person
Person
Boss
Employee
Boss
Employee
CompanyOwner
Boss
Employee
OOP ไม่เกี่ยวกับลำดับชั้นมรดกและกลับมาใช้ของชั้นเรียนที่มีอยู่มันเป็นเรื่องเกี่ยวgeneralizing พฤติกรรม OOP เป็นเรื่องเกี่ยวกับ“ ฉันมีวัตถุมากมายและต้องการให้พวกเขาทำสิ่งใดสิ่งหนึ่ง - และฉันไม่สนใจเลย” นั่นคืออินเทอร์เฟซสำหรับ หากคุณใช้Iterable
อินเทอร์เฟซสำหรับของคุณNode
เพราะคุณต้องการทำให้ iterable นั่นเป็นเรื่องที่สมบูรณ์แบบ หากคุณติดตั้งCollection
ส่วนต่อประสานเพราะคุณต้องการเพิ่ม / ลบโหนดลูกเป็นต้นนั่นก็ถือว่าใช้ได้ แต่สืบทอดมาจากคลาสอื่นเพราะมันเกิดขึ้นเพื่อให้คุณทั้งหมดที่ไม่ได้หรืออย่างน้อยไม่เว้นแต่คุณจะได้ให้มันคิดอย่างรอบคอบตามที่อธิบายไว้ข้างต้น