การสืบทอดหลายรายการใน Java
การสืบทอดหลายรายการทำให้คุณสามารถสร้างคลาสที่สืบทอดมาจากซูเปอร์คลาสหลายรายการได้ ไม่เหมือนกับภาษาการเขียนโปรแกรมเชิงวัตถุยอดนิยมอื่นๆ เช่น C++ Java ไม่อนุญาตให้มีการสืบทอดหลายคลาสจากคลาส Java ไม่รองรับการสืบทอดหลายคลาสเพราะอาจทำให้เกิดปัญหาไดมอนด์ได้ และแทนที่จะมองหาวิธีแก้ปัญหานี้ มีตัวเลือกที่ดีกว่าว่าเราจะบรรลุผลเดียวกันได้อย่างไร เช่น การสืบทอดหลายรายการปัญหาเพชร
เพื่อให้เข้าใจปัญหาเพชรได้ง่ายขึ้น สมมติว่า Java รองรับการสืบทอดหลายรายการ ในกรณีนี้ เราสามารถมีลำดับชั้นของคลาสได้ดังที่แสดงในภาพด้านล่างSuperClass
เป็นนามธรรมและมีการประกาศเมธอดบางอย่างในนั้น ทั้งคลาสคอนกรีตClassA
และClassB
.
package com.journaldev.inheritance;
public abstract class SuperClass {
public abstract void doSomething();
}
package com.journaldev.inheritance;
public class ClassA extends SuperClass{
@Override
public void doSomething(){
System.out.println("doSomething implementation of A");
}
//ClassA own method
public void methodA(){
}
}
package com.journaldev.inheritance;
public class ClassB extends SuperClass{
@Override
public void doSomething(){
System.out.println("doSomething implementation of B");
}
//ClassB specific method
public void methodB(){
}
}
ทีนี้ สมมติ ว่า เราต้องการนำไปใช้ClassC
และสืบทอดมันจากClassA
และClassB
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
public void test(){
//calling super class method
doSomething();
}
}
โปรดทราบว่าเมธอดนี้test()
เรียกเมธอดdoSomething()
superclass สิ่งนี้นำไปสู่ความกำกวมเนื่องจากคอมไพลเลอร์ไม่รู้ว่าจะใช้วิธีซูเปอร์คลาสใดในการดำเนินการ นี่คือแผนภาพคลาสรูปทรงเพชรที่เรียกว่าปัญหาเพชร นี่คือเหตุผลหลักว่าทำไม Java ไม่รองรับการสืบทอดหลายรายการ โปรดทราบว่าปัญหาข้างต้นที่มีการสืบทอดหลายคลาสสามารถเกิดขึ้นได้เฉพาะกับสามคลาสที่มีวิธีการทั่วไปอย่างน้อยหนึ่งวิธี
การสืบทอดหลายอินเทอร์เฟซ
ใน Java คลาสไม่รองรับการสืบทอดหลายรายการ แต่ได้รับการรองรับในอินเทอร์เฟซ และอินเทอร์เฟซเดียวสามารถขยายอินเทอร์เฟซอื่นๆ ได้อีกมากมาย ด้านล่างนี้เป็นตัวอย่างง่ายๆpackage com.journaldev.inheritance;
public interface InterfaceA {
public void doSomething();
}
package com.journaldev.inheritance;
public interface InterfaceB {
public void doSomething();
}
โปรดทราบว่าอินเทอร์เฟซทั้งสองประกาศวิธีการเดียวกัน ตอนนี้เราสามารถสร้างอินเทอร์เฟซที่ขยายอินเทอร์เฟซทั้งสองนี้ได้ ดังแสดงในตัวอย่างด้านล่าง
package com.journaldev.inheritance;
public interface InterfaceC extends InterfaceA, InterfaceB {
//same method is declared in InterfaceA and InterfaceB both
public void doSomething();
}
สิ่งนี้ใช้งานได้ดีเพราะอินเทอร์เฟซประกาศวิธีการเท่านั้นและการนำไปใช้งานจะทำในคลาสที่สืบทอดอินเทอร์เฟซ ดังนั้นจึงไม่มีทางที่จะเกิดความคลุมเครือในการสืบทอดอินเทอร์เฟซหลายรายการได้
package com.journaldev.inheritance;
public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {
@Override
public void doSomething() {
System.out.println("doSomething implementation of concrete class");
}
public static void main(String[] args) {
InterfaceA objA = new InterfacesImpl();
InterfaceB objB = new InterfacesImpl();
InterfaceC objC = new InterfacesImpl();
//all the method calls below are going to same concrete implementation
objA.doSomething();
objB.doSomething();
objC.doSomething();
}
}
โปรดทราบว่าเมื่อใดก็ตามที่คุณแทนที่วิธีซูเปอร์คลาสใดๆ หรือใช้วิธีการอินเทอร์เฟซ ให้ใช้คำอธิบาย@Override
ประกอบ จะเป็นอย่างไรหากเราต้องการใช้ฟังก์ชันmethodA()
ของคลาสClassA
และฟังก์ชันmethodB()
ของคลาสClassB
ในคลาสClassC
? วิธีแก้ปัญหาอยู่ที่การใช้องค์ประกอบ ด้านล่างนี้เป็นเวอร์ชันของคลาสClassC
ที่ใช้การจัดองค์ประกอบเพื่อกำหนดทั้งวิธีการเรียนและวิธีการdoSomething()
ของหนึ่งในอ็อบเจ็กต์
package com.journaldev.inheritance;
public class ClassC{
ClassA objA = new ClassA();
ClassB objB = new ClassB();
public void test(){
objA.doSomething();
}
public void methodA(){
objA.methodA();
}
public void methodB(){
objB.methodB();
}
}
องค์ประกอบเทียบกับมรดก
หนึ่งในแนวทางปฏิบัติในการเขียนโปรแกรม Java ที่ดีที่สุดคือการ “อนุมัติองค์ประกอบก่อนการสืบทอด” เราจะสำรวจบางแง่มุมที่สนับสนุนแนวทางนี้-
สมมติว่าเรามีซูเปอร์คลาสและคลาสที่ขยายออกไป:
package com.journaldev.inheritance; public class ClassC{ public void methodC(){ } } package com.journaldev.inheritance; public class ClassD extends ClassC{ public int test(){ return 0; } }
โค้ดด้านบนคอมไพล์และทำงานได้ดี แต่จะเกิดอะไรขึ้นถ้าเราเปลี่ยนการใช้งานคลาส
ClassC
ดังที่แสดงด้านล่าง:package com.journaldev.inheritance; public class ClassC{ public void methodC(){ } public void test(){ } }
โปรดทราบว่าวิธีการนี้
test()
มีอยู่แล้วในคลาสย่อย แต่ประเภทการส่งคืนจะแตกต่างออกไป ตอนนี้คลาสClassD
จะไม่คอมไพล์และหากคุณใช้ IDE ใด ๆ มันจะแจ้งให้คุณเปลี่ยนประเภทการส่งคืนในซูเปอร์คลาสหรือคลาสย่อยทีนี้ ลองจินตนาการถึงสถานการณ์ที่เรามีลำดับชั้นการสืบทอดคลาสหลายระดับ และไม่สามารถเข้าถึงซูเปอร์คลาสได้ เราจะไม่มีทางเลือกอื่นนอกจากเปลี่ยนลายเซ็นเมธอดคลาสย่อยของเราหรือชื่อเพื่อลบข้อผิดพลาดในการคอมไพล์ เราจะต้องเปลี่ยนเมธอดคลาสย่อยในทุกที่ที่ถูกเรียก ดังนั้นการสืบทอดทำให้โค้ดของเราเปราะ
ปัญหาข้างต้นจะไม่เกิดขึ้นกับการจัดองค์ประกอบ และทำให้การสืบทอดมีความน่าสนใจยิ่งขึ้น
-
ปัญหาอีกประการหนึ่งของการสืบทอดคือเราเปิดเผยวิธีการทั้งหมดของซูเปอร์คลาสให้กับลูกค้า และหากซูเปอร์คลาสของเราไม่ได้รับการออกแบบอย่างเหมาะสมและมีช่องโหว่ด้านความปลอดภัย แม้ว่าเราจะใช้การดำเนินการที่ดีที่สุดของคลาสของเรา เราก็ได้รับผลกระทบจากการใช้งานที่ไม่ดี ของซูเปอร์คลาส องค์ประกอบช่วยเราในการควบคุมการเข้าถึงเมธอดซูเปอร์คลาส ในขณะที่การสืบทอดไม่ได้ให้การควบคุมเมธอดซูเปอร์คลาส นี่เป็นหนึ่งในประโยชน์หลักของการแต่งเพลงจากการสืบทอด
-
ข้อดีอีกประการหนึ่งของการจัดองค์ประกอบคือช่วยให้มีความยืดหยุ่นในวิธีการเรียก การใช้งานคลาส
ClassC
ที่นำเสนอข้างต้นของเรานั้นไม่เหมาะสมและทำให้แน่ใจว่าเวลาการคอมไพล์เชื่อมโยงกับเมธอดที่จะถูกเรียก ด้วยการเปลี่ยนแปลงเพียงเล็กน้อย เราสามารถทำให้การเรียกเมธอดมีความยืดหยุ่นและเป็นไดนามิกได้package com.journaldev.inheritance; public class ClassC{ SuperClass obj = null; public ClassC(SuperClass o){ this.obj = o; } public void test(){ obj.doSomething(); } public static void main(String args[]){ ClassC obj1 = new ClassC(new ClassA()); ClassC obj2 = new ClassC(new ClassB()); obj1.test(); obj2.test(); } }
ผลลัพธ์ของโปรแกรมที่นำเสนอข้างต้น:
doSomething implementation of A doSomething implementation of B
ความยืดหยุ่นในการเรียกเมธอดนี้ไม่สามารถใช้ได้กับการสืบทอด ซึ่งจะเพิ่มข้อดีอีกประการให้กับตัวเลือกการเรียบเรียง
-
การทดสอบหน่วยทำได้ง่ายกว่ากับการจัดองค์ประกอบเพราะเรารู้ว่าเรากำลังใช้วิธีการทั้งหมดจากซูเปอร์คลาสและสามารถคัดลอกวิธีการเหล่านั้นสำหรับการทดสอบได้ ในขณะที่การสืบทอดนั้นเราพึ่งพาซูเปอร์คลาสมากกว่าและไม่ทราบวิธีการทั้งหมดของซูเปอร์คลาสที่จะใช้ ดังนั้นเราจึงต้องทดสอบวิธีการทั้งหมดของซูเปอร์คลาส ซึ่งเป็นงานพิเศษเนื่องจากการสืบทอด
ตามหลักการแล้ว เราควรใช้การสืบทอดเมื่อความสัมพันธ์ของคลาสย่อยถึงซูเปอร์คลาสถูกกำหนดเป็น "เป็น" เท่านั้น ในกรณีอื่นทั้งหมด ขอแนะนำให้ใช้องค์ประกอบนี้
GO TO FULL VERSION