دانستنی‌ها

ماهیت تکامل واسط‌‌ها در جاوا: وراثت چند‌گانه (3)

این مقاله، توسط مایکل کولینگ به رشتۀ تحریر درآمده و در اولین شماره Java Magazine سال 2018 منتشر شده است، نویسنده، سیر تاریخی توسعه و تکامل واسط‌ها را به دقت بررسی می‌کند. بخش سوم از این نوشتار سه قسمتی را در این جا مطالعه کنید.

در بخش قبل، نویسنده، پس از شرح وراثت نوع و وراثت کد، مشکلات مربوط به وراثت چند‌گانه را تنها به وراثت کد نسبت داده و نفع حاصل از وراثت چند‌گانۀ نوع را غیرقابل صرف‌نظر؛ و مشکلات ناشی از آن را قابل حل دانست. سپس کارکرد کلاس های Abstract و واسط های خالی معرفی شد. در این بخش تغییرات واسط‌ها در جاوا 8 شرح داده شده و به مقایسۀ آن با ساختارهای قدیمی پرداخته خواهد شد.

پدیده‌ای جدید در جاوا 8

تا این جا، عمدا برخی از ویژگی‌‌های جدیدی که در جاوا 8 معرفی شده بود را نادیده گرفتیم. دلیل کارمان این بود که در این نسخه امکاناتی اضافه شده که با طراحی‌‌های قبلی زبان مانند «ممنوعیت وراثت چند‌گانۀ کد» تناقض دارد. هنگامی که مسائل جدید را در نظر می‌گیریم شرح روابط بین ساختار‌‌های زبان پیچده می‌شود. به عنوان مثال شرح تفاوت و توجیه طراحی بین دو  ساختار کلاس Abstract و واسط‌‌ها چندان کار ساده‌ای نیست. همان طور که جلوتر توضیح خواهم داد با عرضۀ جاوا 8 واسط‌‌ها تبدیل به چیزی شبیه کلاس‌‌های Abstract شدند، اما با این وجود هنوز تفاوت‌‌های ظریفی وجود دارد.

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

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

واسط‌ها، در حال تحول

یکی از مشکلاتی که طراحان هنگام ارائۀ جاوا 8 با آن درگیر بودند، چگونگی توسعۀ قابلیت‌‌های واسط‌‌های قدیمی بود. جاوا 8 لامدا و چند ویژگی دیگر را اضافه کرد. افزوده شدن آن‌ها می‌طلبید بعضی از واسط‌‌های موجود در کتابخانه‌‌های جاوا تغییر پیدا کنند. اما چگونه می‌توان واسط‌‌ها را تغییر داد بدون این‌که کلاس‌هایی که از این واسط‌ها استفاده کرده‌اند، با مشکل مواجه نشوند؟

تصور کنید واسطی به نام MagicWand در کتابخانۀ خود دارید:

public interface MagicWand {
    void doMagic();
}

این واسط قبلا توسط کلاس‌‌های زیادی در پروژه‌‌های مختلف مورد استفاده قرار گرفته. اما حالا احساس نیاز می‌کنید یک قابلیت خیلی خوب به آن اضافه کنید:

public interface MagicWand {
    void doMagic();
    void doAdvancedMagic();
}

با تغییر فوق، تمام کلاس‌‌هایی که این واسط را پیاده‌سازی کرده‌اند دچار نقص خواهند شد. چرا که باید حاوی پیاده‌سازی برای متدی باشند که قبلا در واسط نبود و حالا اضافه شده است. در نگاه اول به نظر می‌آید گیرافتاده‌ایم و راه برون‌رفتی نیست. یا باید اجرای کد‌‌هایی که از قبل نوشته شده‌اند را با مشکل مواجه کنیم (که مایل به این کار نیستیم) و یا با همان طراحی‌‌های قدیم کنار بیاییم و از توسعۀ کد‌هایمان منصرف شویم. در عمل راه‌‌های دیگری برای فائق آمدن بر مشکل وجود دارد، مثلا می‌توان واسط‌‌های قدیمی را توسعه داد (extend) و در واسط‌‌های جدید قابلیت‌‌های نو را اضافه کرد، این راه‌حل‌‌ها نیز دارای مشکلاتی هستند که این‌جا به آن‌ها‌ نمی‌پردازیم. جاوا 8 از روشی استفاده کرد که هم بتواند واسط‌‌های قدیمی را توسعه‌دهد و هم اجرای کد‌‌های قدیمی را مقدور سازد. اینجا بود که متد‌‌های پیش‌فرض و استاتیک معرفی شدند. در ادامه به مرور آن‌ها خواهیم پرداخت.

متدهای پیش‌فرض

متد‌‌های پیش‌فرض متدهایی هستند که در واسط تعریف می‌شوند و دارای بدنۀ تابع‌اند، یعنی یک پیاده‌سازی پیش فرض دارند. برای مشخص کردن این متد‌‌ها از کلید واژۀ default در ابتدای سطر تعریف استفاده می‌شود و می‌توانند حاوی یک بدنۀ کامل باشند:

public interface MagicWand {
    void doMagic();
    default void doAdvancedMagic() {
    ... // some code here
    }
}

حالا کلاس‌‌هایی که این واسط را پیاده‌سازی می‌کنند، می‌توانند متد را override کرده و بدنۀ مطلوب خود را ارائه کنند و یا کلا وجود آن در واسطِ بالاسری را نادیده بگیرند که در این صورت پیاده‌سازی پیش‌فرض را دریافت خواهند کرد. به این ترتیب، کد‌‌های قبلی به همان شکلی که بودند به کار خود ادامه می‌دهند و کد‌‌های جدید می‌توانند از این قابلیت منتفع شوند.

متد‌‌های استاتیک

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

مشکل وراثت لوزی چه می‌شود؟

همان‌طور که می‌بینید، کلاس‌‌های Abstract و واسط‌‌ها تقریبا شبیه هم شده‌اند. هر دو می‌توانند حاوی متد‌‌های Abstract و متد‌‌های دارای بدنه باشند، هر چند شکل و دستور تعریف آن‌ها متفاوت است. اما هنوز تفاوت‌‌هایی بین این دو وجود دارد. به عنوان مثال کلاس‌‌های Abstract می‌توانند حاوی فیلد‌‌های شی باشند، در حالی که این برای واسط‌‌ها مقدور نیست. اما شرایط موجود در جاوا 8 را می‌توان در یک جمله به این شکل شرح داد: در جاوا 8 می‌توان از طریق واسط ها وراثت چندگانه‌ای داشت که می‌تواند حاوی کد نیز باشد.

در ابتدای این نوشته شرح دادم که طراحان زبان تلاش می‌کردند به خاطر مشکلاتی مانند «وراثت متعدد از چند مسیر» و «ناسازگاری در بدنۀ متد‌‌های هم‌نامِ به ارث رسیده»، از وراثت چند‌گانۀ کد اجتناب کنند. اما حالا چطور؟

طبق معمول، طراحان زبان، تدابیری معقول و عملی اندیشیدند تا بتوانند موقعیت را مدیریت کنند. آنها قوائدی ارائه کردند که به شرح زیر‌است:

  • وراثت چند متد‌ Abstract با نام مشابه بلا‌اشکال است، چرا که به عنوان متد‌‌های یکسان در نظر گرفته می‌شود.
  • از یکی از پیچیده‌ترین مشکلات یعنی وراثت چند‌گانۀ فیلد‌‌ها جلوگیری می‌شود، چرا که واسط‌‌ها نمی‌توانند حاوی فیلد‌‌های غیر‌ثابت باشند.
  • وراثت متد‌‌ها و ثابت‌های استاتیک مشکل‌زا نیست، چرا که هنگام استفاده از آن‌ها مجبور به ذکر نام واسط هستیم؛ در نتیجه تصادم نام متد‌‌ها و فیلد‌‌های استاتیک مشکل‌زا نخواهد بود.
  • وراثت متد‌‌های پیش‌فرض با سطر تعریف مشابه اما بدنۀ متفاوت از مسیر‌‌های وراثت متفاوت مشکل زاست. اما جاوا نسبت به دیگر زبان‌‌های برنامه‌نویسی راه‌حل ساده‌تر و عملی‌تری برای این موقعیت پیشنهاد می‌کند. به جای این که ساختار و قائدۀ پیچیدۀ جدیدی بسازد، فقط شما را با خطای کامپایلر مواجه می‌کند. به عبارت دیگر، این مشکل شماست. جاوا تنها به شما می‌گوید: «این کار را نکن!»

خلاصه و نتیجه‌گیری

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

رفتار جدیدی که در جاوا 8 به واسط‌‌ها اضافه شده در نوشتن کتابخانه‌‌ها بسیار کاربردی است. احتمال استفاده از آن‌ها در کد‌‌های معمولی زیاد نیست. اما کتابخانه‌‌های جاوا می‌توانند به طور گسترده‌ای آن را به کار گیرند. اما ابتدا باید روش درست تعامل با واسط‌‌ها را کاملا درک کنید. استفادۀ دقیق و درست از واسط‌‌ها به مقدار زیادی می‌تواند کیفیت کد‌‌های شما را ارتقا دهد.

منبع

Java Magazine شماره ی ژانویه و فوریه ی 2018

مجلۀ جاوا یا Java Magazine دوماهنامه‌ای است که فقط به صورت الکترونیکی منتشر می شود. این نشریه یک منبع عالی برای مطالعه در مورد تکنولوژی و زبان برنامه‌نویسی جاواست. کتابخانه‌ها و نرم‌افزار‌های متکی بر جاوا نیز در این نشریه دیده می‌شوند.  اخبار جامعه کاربران، کتاب‌های جدید، کنفرانس‌ها و رویداد‌های آتی، از بخش های دیگری است که پوشش داده می‌شود.

این مجلۀ الکترونیکی قریب به 150 هزار مشترک مستقیم دارد. انتشار مقالات کاملا کاربردی و مرتبط با متن صنعت روز توسط متخصصین فعال تکنولوژی، از دلایل موفقیت این مجله به شمار می‌رود. مقالات مذکور توسط مهندسانی از شرکت اوراکل و کل جامعۀ کاربری به دست مدیران نشریه می‌رسد. جهت عضویت رایگان و دریافت این نشریه، می توانید به این لینک مراجعه کنید.

مشاهده بخش اول مقاله

مشاهده بخش دوم مقاله

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا