وراثت چندگانه در جاوا
وراثت چندگانه به شما امکان می دهد کلاسی ایجاد کنید که از چندین سوپرکلاس ارث می برد. برخلاف برخی دیگر از زبان های برنامه نویسی شی گرا مانند C++، جاوا اجازه ارث بردن چندگانه از کلاس ها را نمی دهد. جاوا از وراثت چند کلاسه پشتیبانی نمی کند زیرا می تواند منجر به مشکل الماس شود. و به جای جستجوی راههایی برای حل این مشکل، گزینههای بهتری وجود دارد که چگونه میتوانیم به همان نتیجه مانند ارث چندگانه دست یابیم.مشکل الماس
برای درک آسانتر مشکل الماس، اجازه دهید فرض کنیم که وراثت چندگانه در جاوا پشتیبانی می شود. در این حالت، میتوانیم یک سلسله مراتب طبقاتی داشته باشیم که در تصویر زیر نشان داده شده است. فرض کنید کلاس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()
متد superclass را فراخوانی می کند doSomething()
. این منجر به ابهام می شود زیرا کامپایلر نمی داند کدام متد سوپرکلاس را اجرا کند. این یک نمودار کلاس الماس شکل است که به آن مشکل الماس می گویند. این دلیل اصلی عدم پشتیبانی جاوا از وراثت چندگانه است. توجه داشته باشید که مشکل فوق با وراثت چند کلاسی فقط می تواند با سه کلاس که حداقل یک متد مشترک دارند اتفاق بیفتد.
وراثت چند رابط
در جاوا، وراثت چندگانه در کلاس ها پشتیبانی نمی شود، اما در رابط ها پشتیبانی می شود. و یک رابط می تواند بسیاری از رابط های دیگر را گسترش دهد. در زیر یک مثال ساده آورده شده است.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();
}
}
ترکیب در مقابل وراثت
یکی از بهترین روشهای برنامهنویسی جاوا، «تأیید ترکیب قبل از وراثت» است. ما برخی از جنبه هایی را که به نفع این رویکرد است بررسی خواهیم کرد.-
فرض کنید یک سوپرکلاس و یک کلاس داریم که آن را گسترش می دهد:
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 استفاده میکنید، از شما میخواهد که نوع بازگشت را در سوپرکلاس یا زیر کلاس تغییر دهید.حال شرایطی را تصور کنید که در آن ما یک سلسله مراتب ارث بری کلاس چند سطحی داریم و به سوپرکلاس دسترسی نداریم. ما چاره ای نخواهیم داشت جز اینکه امضای متد زیر کلاس یا نام آن را برای حذف خطای کامپایل تغییر دهیم. همچنین باید متد subclass را در همه جاهایی که فراخوانی می شود تغییر دهیم. بنابراین، وراثت کد ما را شکننده می کند.
مشکل فوق هرگز در ترکیب اتفاق نمی افتد و این باعث می شود که ارثی جذابیت بیشتری داشته باشد.
-
مشکل دیگر وراثت این است که ما تمام متدهای سوپرکلاس را در معرض دید کلاینت قرار می دهیم و اگر سوپرکلاس ما به درستی طراحی نشده باشد و حفره های امنیتی وجود داشته باشد، حتی اگر بهترین پیاده سازی کلاس خود را پیاده سازی کنیم، تحت تاثیر پیاده سازی ضعیف قرار می گیریم. از سوپرکلاس . ترکیب به ما در ارائه دسترسی کنترلشده به روشهای سوپرکلاس کمک میکند، در حالی که وراثت کنترلی بر روشهای سوپرکلاس فراهم نمیکند. این نیز یکی از مزایای اصلی ترکیب از ارث است.
-
مزیت دیگر ترکیب این است که امکان انعطاف پذیری در فراخوانی روش ها را فراهم می کند. پیاده سازی ما از کلاس
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
این انعطاف پذیری در فراخوانی متد با وراثت در دسترس نیست، که مزیت دیگری به انتخاب ترکیب میافزاید.
-
انجام تست واحد با ترکیب آسانتر است، زیرا میدانیم که از تمام روشهای سوپرکلاس استفاده میکنیم و میتوانیم آنها را برای آزمون کپی کنیم. در حالی که در وراثت بیشتر به superclass وابسته هستیم و همه روش های superclass را که مورد استفاده قرار می گیرد نمی دانیم. بنابراین ما باید تمام روش های سوپرکلاس را آزمایش کنیم که به دلیل وراثت کار اضافی است.
در حالت ایدهآل، ما فقط زمانی باید از وراثت استفاده کنیم که رابطه زیر کلاس به سوپرکلاس به صورت "is" تعریف شده باشد. در تمام موارد دیگر، استفاده از ترکیب توصیه می شود.
GO TO FULL VERSION