JavaRush /وبلاگ جاوا /Random-FA /وراثت چندگانه در جاوا مقایسه ترکیب و ارث
HonyaSaar
مرحله
Москва

وراثت چندگانه در جاوا مقایسه ترکیب و ارث

در گروه منتشر شد
چند وقت پیش چندین پست در مورد وراثت، رابط ها و ترکیب در جاوا نوشتم. در این مقاله به ارث چندگانه می پردازیم و سپس با مزایای ترکیب نسبت به ارث آشنا می شویم.
وراثت چندگانه در جاوا  مقایسه ترکیب و ارث - 1

وراثت چندگانه در جاوا

وراثت چندگانه توانایی ایجاد کلاس هایی با چندین کلاس والد است. بر خلاف سایر زبان های شی گرا محبوب مانند C++، جاوا از وراثت چند کلاسه پشتیبانی نمی کند. او به دلیل احتمال مواجهه با «مشکل الماس» از آن پشتیبانی نمی کند و در عوض ترجیح می دهد نوعی رویکرد جامع برای حل آن ارائه دهد، با استفاده از بهترین گزینه ها که بتوانیم به نتیجه ارثی مشابهی دست یابیم.

"مشکل الماس"

برای درک ساده تر مشکل الماس، اجازه دهید فرض کنیم که وراثت چندگانه در جاوا پشتیبانی می شود. در این صورت می توانیم کلاس هایی را با سلسله مراتبی که در شکل زیر نشان داده شده است دریافت کنیم. سلسله مراتب کلاس الماسبیایید فرض کنیم که SuperClassیک کلاس انتزاعی است که یک متد خاص را توصیف می کند و کلاس ها ClassAو ClassBکلاس های واقعی هستند. SuperClass.java
package com.journaldev.inheritance;
public abstract class SuperClass {
   	public abstract void doSomething();
}
ClassA.java
package com.journaldev.inheritance;
public class ClassA extends SuperClass{
    @Override
 public void doSomething(){
        System.out.println("Какая-то реализация класса A");
    }
  //собственный метод класса  ClassA
    public void methodA(){
    }
}
حال، فرض می کنیم که کلاس ClassCاز ClassAو ClassBدر همان زمان ارث می برد و در همان زمان پیاده سازی زیر را دارد:
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
    public void test(){
        //вызов метода родительского класса
        doSomething();
    }
}
توجه داشته باشید که متد متدی از کلاس والد test()را فراخوانی می کند doSomething()که منجر به ابهام می شود زیرا کامپایلر نمی داند کدام متد سوپرکلاس باید فراخوانی شود. با توجه به شکل نمودار وراثت کلاس در این شرایط، که شبیه طرح کلی یک الماس وجهی است، این مشکل "مسئله الماس" نامیده می شود. این دلیل اصلی عدم پشتیبانی جاوا از وراثت چند کلاسه است. توجه داشته باشید که این مشکل با ارث بردن چند کلاس می تواند با سه کلاس که حداقل یک متد مشترک دارند نیز رخ دهد.

وراثت و رابط های چندگانه

ممکن است متوجه شده باشید که من همیشه می گویم "ارث بری چندگانه بین کلاس ها پشتیبانی نمی شود"، اما بین اینترفیس ها پشتیبانی می شود. یک مثال ساده در زیر نشان داده شده است: InterfaceA.java
package com.journaldev.inheritance;
public interface InterfaceA {

    public void doSomething();
}
InterfaceB.java
package com.journaldev.inheritance;

public interface InterfaceB {

    public void doSomething();
}
توجه داشته باشید که هر دو اینترفیس متدی با نام یکسان دارند. حال فرض کنید یک اینترفیس داریم که از هر دو اینترفیس ارث می برد. InterfaceC.java
package com.journaldev.inheritance;

public interface InterfaceC extends InterfaceA, InterfaceB {

    //метод, с тем же названием описан в  InterfaceA и InterfaceB
    public void doSomething();
در اینجا همه چیز ایده‌آل است، زیرا اینترفیس‌ها فقط رزرو/توضیح یک متد هستند و پیاده‌سازی خود متد در کلاس بتنی خواهد بود که این رابط‌ها را پیاده‌سازی می‌کند، بنابراین امکان مواجهه با ابهام با ارث بری چندگانه رابط‌ها وجود ندارد. به همین دلیل است که کلاس ها در جاوا می توانند از چندین رابط ارث بری کنند. بیایید آن را با مثال زیر نشان دهیم. InterfacesImpl.java
package com.journaldev.inheritance;

public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {

    @Override
    public void doSomething() {
        System.out.println("doSomething реализация реального класса ");
    }

    public static void main(String[] args) {
        InterfaceA objA = new InterfacesImpl();
        InterfaceB objB = new InterfacesImpl();
        InterfaceC objC = new InterfacesImpl();

        //все вызываемые ниже методы получат одинаковую реализацию конкретного класса

        objA.doSomething();
        objB.doSomething();
        objC.doSomething();
    }
}
ممکن است متوجه شده باشید که هر بار که من روشی را که در یک سوپرکلاس یا یک رابط توضیح داده شده لغو می کنم، از حاشیه نویسی @Override استفاده می کنم. این یکی از سه حاشیه نویسی جاوا داخلی است و همیشه باید هنگام نادیده گرفتن روش ها از آن استفاده کنید.

ترکیب به عنوان نجات

پس اگر بخواهیم از یک methodA()کلاس ClassAو تابع methodB()کلاس ClassBدر استفاده کنیم چه می شود ClassС؟ یک راه حل برای این می تواند ترکیب باشد - یک نسخه بازنویسی شده ClassCکه هر دو متد کلاس را پیاده سازی می کند ClassAو ClassBهمچنین یک پیاده سازی doSomething()برای یکی از اشیاء دارد. ClassC.java
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();
    }
}

ترکیب یا ارث؟

این تمرین خوب برنامه نویسی جاوا است که از مزیت ترکیب نسبت به وراثت استفاده کنید. ما به برخی از جنبه ها به نفع این رویکرد نگاه خواهیم کرد.
  1. فرض کنید ترکیب زیر از کلاس‌های والدین-وارث را داریم:

    ClassC.java

    package com.journaldev.inheritance;
    
    public class ClassC{
    
    public void methodC(){
      	}
    
    }

    ClassD.java

    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()از قبل در کلاس descendant وجود دارد، اما نتیجه ای از نوع متفاوتی را برمی گرداند. اکنون ClassD، در صورتی که از یک IDE استفاده می کنید، کامپایل نمی شود. به شما توصیه می شود که نوع بازگشت را در نسل یا سوپرکلاس تغییر دهید.

    حال بیایید شرایطی را تصور کنیم که در آن وراثت چند سطحی کلاس ها وجود داشته باشد و سوپرکلاس برای تغییرات ما در دسترس نباشد. حال برای رهایی از خطای کامپایل، گزینه دیگری جز تغییر امضا یا نام متد subclass نداریم. همچنین باید در تمام مکان هایی که این متد فراخوانی شده است تغییراتی ایجاد کنیم. بنابراین، وراثت کد ما را شکننده می کند.

    مشکلی که در بالا توضیح داده شد هرگز در مورد ترکیب رخ نمی دهد و بنابراین دومی را بر ارث ترجیح می دهد.

  2. مشکل بعدی وراثت این است که همه روش‌های والد را در معرض دید مشتری قرار می‌دهیم. و اگر سوپرکلاس خیلی درست طراحی نشده باشد و حفره های امنیتی داشته باشد. سپس، حتی اگر در اجرای زیر کلاس خود از امنیت کامل مراقبت کنیم، همچنان به اجرای معیوب کلاس والد وابسته خواهیم بود.

    ترکیب به ما در ارائه دسترسی کنترل‌شده به روش‌های یک سوپرکلاس کمک می‌کند، در حالی که وراثت هیچ کنترلی بر روش‌های آن حفظ نمی‌کند. این نیز یکی از مزایای اصلی ترکیب نسبت به ارث است.

  3. یکی دیگر از مزایای ترکیب این است که هنگام فراخوانی متدها، انعطاف‌پذیری را افزایش می‌دهد. پیاده سازی کلاس ClassCتوضیح داده شده در بالا بهینه نیست و از اتصال اولیه به متد فراخوانی شده استفاده می کند. تغییرات حداقلی به ما این امکان را می‌دهد که فراخوانی متد را انعطاف‌پذیر کنیم و امکان اتصال دیرهنگام (باید در زمان اجرا) را فراهم کنیم.

    ClassC.java

    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

    این انعطاف پذیری در فراخوانی متد با وراثت دیده نمی شود، که ترکیب را بهترین رویکرد می کند.

  4. تست واحد در مورد ترکیب آسان‌تر است، زیرا می‌دانیم که برای همه روش‌هایی که در سوپرکلاس استفاده می‌شود، می‌توانیم تست‌ها را خرد کنیم، در حالی که در وراثت به شدت به سوپرکلاس وابسته هستیم و نمی‌دانیم روش‌های کلاس والد چگونه است. استفاده خواهد شد. بنابراین، به دلیل ارثی، باید تمام روش های سوپرکلاس را آزمایش کنیم که کار غیر ضروری است.

    در حالت ایده‌آل، وراثت فقط زمانی باید استفاده شود که رابطه « is-a » برای کلاس‌های والد و فرزند صادق باشد، در غیر این صورت ترکیب باید ترجیح داده شود.

مقاله اصلی
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION