CodeGym /وبلاگ جاوا /Random-FA /چند شکلی جاوا
John Squirrels
مرحله
San Francisco

چند شکلی جاوا

در گروه منتشر شد
سوالات مربوط به OOP بخشی جدایی ناپذیر از مصاحبه فنی برای موقعیت توسعه دهنده جاوا در یک شرکت فناوری اطلاعات است. در این مقاله، ما در مورد یک اصل OOP - چند شکلی صحبت خواهیم کرد. ما بر جنبه‌هایی تمرکز می‌کنیم که اغلب در طول مصاحبه‌ها درباره آنها سؤال می‌شود، و همچنین چند مثال برای وضوح ارائه می‌کنیم.

پلی مورفیسم در جاوا چیست؟

Polymorphism توانایی یک برنامه برای برخورد با اشیاء با رابط یکسان به روشی یکسان، بدون اطلاعات در مورد نوع خاص شی است. اگر به سؤالی در مورد چندشکلی پاسخ دهید، به احتمال زیاد از شما خواسته می شود منظور خود را توضیح دهید. بدون ایجاد یک سری سؤالات اضافی، یک بار دیگر همه چیز را برای مصاحبه کننده مطرح کنید. زمان مصاحبه: چندشکلی در جاوا - 1می توانید با این واقعیت شروع کنید که رویکرد OOP شامل ساخت یک برنامه جاوا بر اساس تعامل بین اشیاء است که بر اساس کلاس ها هستند. کلاس ها طرح هایی هستند که قبلاً نوشته شده اند (الگوها) که برای ایجاد اشیاء در برنامه استفاده می شوند. علاوه بر این، یک کلاس همیشه دارای یک نوع خاص است که با سبک برنامه نویسی خوب، نامی دارد که هدف آن را نشان می دهد. علاوه بر این، می توان اشاره کرد که از آنجایی که جاوا به شدت تایپ می شود، کد برنامه همیشه باید یک نوع شی را در هنگام اعلان متغیرها مشخص کند. به این واقعیت اضافه کنید که تایپ دقیق، امنیت و قابلیت اطمینان کد را بهبود می بخشد، و حتی در هنگام کامپایل، جلوگیری از خطاهای ناشی از انواع ناسازگاری (به عنوان مثال، تلاش برای تقسیم یک رشته بر یک عدد) را ممکن می سازد. به طور طبیعی، کامپایلر باید نوع اعلام شده را "بشناسد" - می تواند کلاسی از JDK یا کلاسی باشد که خودمان ایجاد کرده ایم. به مصاحبه کننده اشاره کنید که کد ما نه تنها می تواند از اشیاء نوع مشخص شده در اعلان استفاده کند، بلکه می تواند از نوادگان آن نیز استفاده کند. این یک نکته مهم است: ما می توانیم با بسیاری از انواع مختلف به عنوان یک نوع کار کنیم (به شرطی که این انواع از یک نوع پایه مشتق شده باشند). این همچنین به این معنی است که اگر متغیری را که نوع آن یک سوپرکلاس است، اعلام کنیم، می‌توانیم نمونه‌ای از یکی از فرزندان آن را به آن متغیر اختصاص دهیم. اگر مثالی بزنید، مصاحبه کننده از آن خوشش می آید. کلاسی را انتخاب کنید که می تواند توسط (یک کلاس پایه برای) چندین کلاس به اشتراک گذاشته شود و چند نفر از آنها را به ارث ببرند. کلاس پایه:
public class Dancer {
    private String name;
    private int age;

    public Dancer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void dance() {
        System.out.println(toString() + " I dance like everyone else.");
    }

    @Override
    public String toString() {
        Return "I'm " + name + ". I'm " + age + " years old.";
    }
}
در زیر کلاس‌ها، متد کلاس پایه را لغو کنید:
public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// Override the method of the base class
    @Override
    public void dance() {
        System.out.println(toString () + " I dance the electric boogie!");
    }
}

public class Breakdancer extends Dancer {

    public Breakdancer(String name, int age) {
        super(name, age);
    }
// Override the method of the base class
    @Override
    public void dance() {
        System.out.println(toString() + " I breakdance!");
    }
}
مثالی از چندشکلی و نحوه استفاده از این اشیاء در یک برنامه:
public class Main {

    public static void main(String[] args) {
        Dancer dancer = new Dancer("Fred", 18);

        Dancer breakdancer = new Breakdancer("Jay", 19); // Widening conversion to the base type
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20); // Widening conversion to the base type

        List<dancer> disco = Arrays.asList(dancer, breakdancer, electricBoogieDancer);
        for (Dancer d : disco) {
            d.dance(); // Call the polymorphic method
        }
    }
}
در روش اصلی نشان دهید که خطوط
Dancer breakdancer = new Breakdancer("Jay", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20);
یک متغیر از یک سوپرکلاس را اعلان کنید و یک شی را به آن اختصاص دهید که نمونه ای از یکی از فرزندان آن است. به احتمال زیاد از شما پرسیده خواهد شد که چرا کامپایلر با ناهماهنگی انواع اعلام شده در سمت چپ و راست عملگر تخصیص، باز نمی شود – بالاخره جاوا به شدت تایپ شده است. توضیح دهید که یک تبدیل نوع گسترده در اینجا کار می کند - ارجاع به یک شی مانند یک مرجع به کلاس پایه آن در نظر گرفته می شود. علاوه بر این، کامپایلر با مواجه شدن با چنین ساختاری در کد، تبدیل را به صورت خودکار و ضمنی انجام می دهد. کد نمونه نشان می دهد که نوع اعلام شده در سمت چپ عملگر انتساب ( Dancer ) دارای چندین فرم (انواع) است که در سمت راست ( Breakdancer , ElectricBoogieDancer ) اعلان شده است. هر فرم می تواند رفتار منحصر به فرد خود را با توجه به عملکرد کلی تعریف شده در سوپرکلاس ( روش رقص ) داشته باشد. یعنی یک متد اعلام شده در یک سوپرکلاس ممکن است به طور متفاوتی در فرزندان آن پیاده سازی شود. در این مورد، ما با روش overriding روبرو هستیم که دقیقاً همان چیزی است که چندین فرم (رفتار) ایجاد می کند. این را می توان با اجرای کد در روش اصلی مشاهده کرد: خروجی برنامه: I'm Fred. من 18 سالمه من هم مثل بقیه می رقصم. من جی هستم. من 19 سال دارم. من بریک رقصم! من مارسیا هستم. من 20 ساله هستم. من بوگی برقی می رقصم! اگر متد را در زیر کلاس‌ها لغو نکنیم، رفتار متفاوتی نخواهیم داشت. برای مثال، اگر روش رقص را در کلاس های Breakdancer و ElectricBoogieDancer خود بیان کنیم ، خروجی برنامه این خواهد بود: I'm Fred. من 18 سالمه من هم مثل بقیه می رقصم. من جی هستم. من 19 سال دارم. من هم مثل بقیه می رقصم. من مارسیا هستم. من 20 ساله هستم. من هم مثل بقیه می رقصم. و این بدان معنی است که ایجاد کلاس های Breakdancer و ElectricBoogieDancer به سادگی منطقی نیست . به طور خاص اصل چندشکلی در کجا آشکار می شود؟ یک شیء بدون اطلاع از نوع خاص آن در کجای برنامه استفاده می شود؟ در مثال ما، زمانی اتفاق می افتد که متد dance() روی شی Dancer d فراخوانی شود . در جاوا، چند شکلی به این معنی است که برنامه نیازی به دانستن اینکه آیا شی Breakdancer است یا ElectricBoogieDancer است . مهم این است که از نوادگان کلاس Dancer است . و اگر از فرزندان نام می برید، باید توجه داشته باشید که وراثت در جاوا فقط به صورت Extend نیست ، بلکه پیاده سازی نیز می شود.. اکنون زمان ذکر این نکته است که جاوا از وراثت چندگانه پشتیبانی نمی کند - هر نوع می تواند یک والد (سوپر کلاس) و تعداد نامحدودی از فرزندان (زیر کلاس) داشته باشد. بر این اساس، اینترفیس ها برای افزودن چندین مجموعه از توابع به کلاس ها استفاده می شوند. در مقایسه با کلاس های فرعی (ارثی)، رابط ها کمتر با کلاس والد همراه هستند. آنها بسیار گسترده استفاده می شوند. در جاوا، یک رابط یک نوع مرجع است، بنابراین برنامه می تواند یک متغیر از نوع رابط را اعلام کند. حالا وقت آن است که مثال بزنیم. ایجاد یک رابط:
public interface CanSwim {
    void swim();
}
برای وضوح، کلاس‌های مختلف نامرتبط را می‌گیریم و آنها را وادار می‌کنیم که رابط را پیاده‌سازی کنند:
public class Human implements CanSwim {
    private String name;
    private int age;

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void swim() {
        System.out.println(toString()+" I swim with an inflated tube.");
    }

    @Override
    public String toString() {
        return "I'm " + name + ". I'm " + age + " years old.";
    }

}

public class Fish implements CanSwim {
    private String name;

    public Fish(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println("I'm a fish. My name is " + name + ". I swim by moving my fins.");

    }

public class UBoat implements CanSwim {

    private int speed;

    public UBoat(int speed) {
        this.speed = speed;
    }

    @Override
    public void swim() {
        System.out.println("I'm a submarine that swims through the water by rotating screw propellers. My speed is " + speed + " knots.");
    }
}
روش اصلی :
public class Main {

    public static void main(String[] args) {
        CanSwim human = new Human("John", 6);
        CanSwim fish = new Fish("Whale");
        CanSwim boat = new UBoat(25);

        List<swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
نتایج فراخوانی یک روش چندشکلی تعریف شده در یک رابط، تفاوت‌های رفتار انواعی که این رابط را پیاده‌سازی می‌کنند را به ما نشان می‌دهد. در مورد ما، اینها رشته های مختلفی هستند که با روش شنا نمایش داده می شوند . پس از مطالعه مثال ما، مصاحبه کننده ممکن است بپرسد که چرا این کد را در روش اصلی اجرا می کنید
for (Swim s : swimmers) {
            s.swim();
}
باعث می شود متدهای overriding تعریف شده در زیر کلاس های ما فراخوانی شوند؟ پیاده سازی مورد نظر روش در حین اجرای برنامه چگونه انتخاب می شود؟ برای پاسخ به این سوالات باید صحافی دیررس (پویا) را توضیح دهید. Binding به معنای ایجاد یک نقشه بین فراخوانی متد و اجرای کلاس خاص آن است. در اصل کد تعیین می کند که کدام یک از سه متد تعریف شده در کلاس ها اجرا شود. جاوا به طور پیش‌فرض از اتصال دیرهنگام استفاده می‌کند، یعنی اتصال در زمان اجرا اتفاق می‌افتد و نه در زمان کامپایل، همانطور که در پیوند اولیه وجود دارد. به این معنی که وقتی کامپایلر این کد را کامپایل می کند
for (Swim s : swimmers) {
            s.swim();
}
نمی داند کدام کلاس ( Human ، Fish یا Uboat ) دارای کدی است که با فراخوانی متد swim اجرا می شود. این تنها زمانی مشخص می شود که برنامه اجرا می شود، به لطف مکانیزم اتصال پویا (بررسی نوع یک شی در زمان اجرا و انتخاب پیاده سازی صحیح برای این نوع). اگر از شما پرسیده شد که این چگونه پیاده سازی می شود، می توانید پاسخ دهید که هنگام بارگذاری و مقداردهی اولیه اشیا، JVM جداول را در حافظه می سازد و متغیرها را با مقادیر آنها و اشیاء را با متدهای آنها پیوند می دهد. در انجام این کار، اگر یک کلاس به ارث می رسد یا یک رابط را پیاده سازی می کند، اولین کار این است که وجود متدهای نادیده گرفته شده را بررسی کنید. در صورت وجود، آنها به این نوع مقید هستند. در غیر این صورت، جستجو برای یک متد تطبیق به کلاسی که یک پله بالاتر است (والد) و به همین ترتیب تا ریشه در یک سلسله مراتب چند سطحی حرکت می کند. وقتی صحبت از چندشکلی در OOP و پیاده‌سازی آن در کد می‌شود، توجه می‌کنیم که استفاده از کلاس‌ها و رابط‌های انتزاعی برای ارائه تعاریف انتزاعی از کلاس‌های پایه، تمرین خوبی است. این عمل از اصل انتزاع ناشی می شود - شناسایی رفتار و ویژگی های مشترک و قرار دادن آنها در یک کلاس انتزاعی، یا شناسایی تنها رفتار مشترک و قرار دادن آن در یک رابط. طراحی و ایجاد یک سلسله مراتب شی بر اساس رابط ها و وراثت کلاس برای پیاده سازی چند شکلی مورد نیاز است. با توجه به چندشکلی و نوآوری‌ها در جاوا، یادآور می‌شویم که با شروع با جاوا 8، هنگام ایجاد کلاس‌ها و رابط‌های انتزاعی، می‌توان از کلمه کلیدی پیش‌فرض برای نوشتن پیاده‌سازی پیش‌فرض برای متدهای انتزاعی در کلاس‌های پایه استفاده کرد. مثلا:
public interface CanSwim {
    default void swim() {
        System.out.println("I just swim");
    }
}
گاهی اوقات مصاحبه کنندگان می پرسند که چگونه روش ها در کلاس های پایه باید اعلام شود تا اصل چندشکلی نقض نشود. پاسخ ساده است: این روش ها نباید ثابت ، خصوصی و نهایی باشند . Private یک روش را فقط در یک کلاس در دسترس قرار می دهد، بنابراین نمی توانید آن را در یک کلاس فرعی لغو کنید. Static یک متد را به جای هر شیء با کلاس مرتبط می کند، بنابراین متد superclass همیشه فراخوانی می شود. و final یک متد را تغییرناپذیر و از زیر کلاس‌ها پنهان می‌کند.

چندشکلی چه چیزی به ما می دهد؟

همچنین به احتمال زیاد از شما در مورد اینکه پلی مورفیسم چگونه برای ما سودمند است سؤال می شود. شما می توانید به طور خلاصه به این پاسخ بدون غرق شدن در جزئیات مودار پاسخ دهید:
  1. این امکان جایگزینی پیاده سازی های کلاس را فراهم می کند. تست بر روی آن ساخته شده است.
  2. توسعه پذیری را تسهیل می کند و ایجاد پایه ای را که در آینده می توان بر آن بنا کرد بسیار آسان تر می کند. افزودن انواع جدید بر اساس انواع موجود رایج ترین راه برای گسترش عملکرد برنامه های OOP است.
  3. به شما امکان می دهد اشیایی را که یک نوع یا رفتار مشترک دارند را در یک مجموعه یا آرایه ترکیب کنید و آنها را به طور یکنواخت مدیریت کنید (مانند نمونه های ما، جایی که ما همه را مجبور به رقصیدن() یا شنا کردن() کردیم :)
  4. انعطاف‌پذیری در ایجاد انواع جدید: می‌توانید پیاده‌سازی یک متد را توسط والدین انتخاب کنید یا آن را در یک زیر کلاس لغو کنید.

چند کلمه جدایی

چندشکلی موضوع بسیار مهم و گسترده ای است. این موضوع تقریباً نیمی از این مقاله در مورد OOP در جاوا است و بخش خوبی از پایه زبان را تشکیل می دهد. شما نمی توانید از تعریف این اصل در مصاحبه اجتناب کنید. اگر آن را نمی دانید یا آن را درک نمی کنید، احتمالاً مصاحبه به پایان می رسد. بنابراین سست نباشید - قبل از مصاحبه دانش خود را ارزیابی کنید و در صورت لزوم آن را تجدید کنید.
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION