آموزش برنامه نویسی جاوا: ارث بری
ارثبری یکی از مباحث مهم در مبحث شی گرایی است. در برنامهنویسی نیز این واژه مفهمومی مشابه دنیای واقعی دارد و به معنای ارث بردن خصوصیات است. جدا از رفتارها و خصوصیاتی که فرزندان از والدین خود به ارث میبرند، رفتارهایی متفاوت و جدیدتری نیز در فرزندان وجود دارد که به نوعی میتوان گفت که فرزندان، نوع کاملتری از پدران و مادران خود هستند. به عنوان مثال موتور سیکلت نوع کاملتری از دوچرخه است. یعنی تمام خصوصیات دوچرخه را دارد و علاوه بر آن یکسری خصوصیات جدیدتری به آن اضافه شده است.
ارث بری (Inheritance)
یکی از نکات مهمی که در مهندسی نرم افزار مطرح میشود، این است که از کدهایی که در برنامه نوشتهایم، «استفادهی مجدد از کد» یا «Code Reuse» کنیم. به این معنی که اگر در قسمتی از برنامه مجبور شدیم کدهایی بنویسیم که قبلا در همان برنامه نوشتهایم، دیگر آن کدها را باز نویسی نکنیم و روشهایی را بکار بگیریم که بتوانیم از همان کدها دوباره استفاده کنیم (بدون نوشتن مجدد کدها). در واقع بزرگترین اشتباه در برنامه نویسی که معمولا برنامه نویسان مبتدی بسیار آن را انجام میدهند، کُپی کردن است که به شدت باید با این قضیه جدی برخورد کنید و تحت هیچ شرایطی کُپی نکنید. یکی از روشهای استفادهی مجدد از کد، ارث بری است. یعنی ما میتوانیم از کلاسهایی که قبلا ایجاد کردهایم، ارث بری کنیم و از ویژگیها و رفتارهای آن کلاسها، در کلاسهای دیگر استفاده کنیم.
چند نکته در مورد ارث بری
ابتدا به عکس زیر توجه کنید:
در تصویر بالا یک سلسله مراتب کوچکی از حیوانات است. حیوانات به دستههای زیادی تقسیمبندی میشوند. مثلا پستانداران، پرندگان، خزندگان و ... که همگی زیر مجموعهی «حیوان» هستند. بنابراین میتوان حیوان را به عنوان والد (پدر) در نظر گرفت و زیر مجموعهها را فرزند. در برنامه نویسی اصطلاحا به کلاسهای والد، اَبَر کلاس یا (Super Class) و به کلاسهای فرزند زیر کلاس یا (Subclass) میگویند. در تصویر بالا Animal والد است و پرندگان یا پستاندارن، فرزند هستند. نکتهای که وجود دارد، یک کلاس فرزند میتواند همزمان هم والد باشد و هم فرزند. به عنوان مثال مهرهداران هم فرزند حیوان هستند و هم والد ماهیها، پرندگان، پستانداران و ... . توجه داشته باشید که هرچه از سمت والد به سمت فرزند حرکت میکنیم، با مجموعهی محدودتری رو به رو میشویم. به عنوان مثال کلاس حیوان شامل تمام حیوانات است اما کلاس پرندگان فقط شامل پرندگان است. بنابراین هرچه پایینتر میرویم، با مجموعهی کمتر، محدودتر و خاصتری رو به رو میشویم.
به کلاس اصلی (که والد است) اصطلاحا:
- کلاس پایه (Base Class)
- اَبَر کلاس (Super Class)
- کلاس والد (Parent Class)
و به کلاس وارث اصطلاحا:
- کلاس مشتق (Derived Class)
- زیر کلاس (Subclass)
- کلاس فرزند (Child Class)
اصطلاحات انگلیسی بسیار مهم هستند و به ترتیبی که نوشته شده است به کار میروند. به عنوان مثال کلاس پایه و کلاس مشتق و غیره.
استفاده از مفهوم ارث بری در برنامه نویسی بسیار ساده است. در آموزشهای قبلی این دورهی آموزشی، با مفاهیمی مانند کلاسها، ویژگیها (خصوصیات) و متدها (رفتارها) آشنا شدهاید. هنگامی که یک کلاسی را تعریف میکنیم، در آن کلاس ویژگیها و رفتارهایی را هم مشخص میکنیم. حالا اگر یک کلاس دیگر تعریف کنیم و بخواهیم از یک کلاس دیگری ارث بری کند، باید از کلمهی کلیدی extends استفاده کنیم. به کد زیر توجه کنید:
package ir.zoomit;public class Person { String name; int age;}
در بالا یک کلاسی ایجاد کردهایم با نام Person که این کلاس دارای ویژگیهای name و age است. حالا میخواهیم کلاس دیگری ایجاد کنیم با نام Student که از کلاس Person ارث بری کند. کد زیر:
package ir.zoomit;public class Student extends Person {}
همانطور که مشاهده میکنید با استفاده از کلیدواژهی extends از کلاس Person ارث بری کردهایم.
حالا برای اینکه مطمئن شویم که ما در کلاس Student به فیلدهای (ویژگیهای) کلاس Person دسترسی داریم، یک متد در کلاس Student تعریف میکنیم و سپس یکی از فیلدهای کلاس Person را در خروجی استاندارد چاپ میکنیم. کد زیر:
package ir.zoomit;public class Student extends Person { public void show() { System.out.println(name); }}
حالا اگر خواستار اجرای برنامه هستید، در متد main یک آبجکت از روی کلاس Student ایجاد کنید و سپس متد ()show را فراخوانی کنید. البته خروجی این برنامه مقدار null است، اما برای تمرین بیشتر این کار را خودتان انجام دهید.
یکی از نکات مثبت استفاده از Code Reuse را در کد بالا میتوان مشاهده کرد. فرض کنید پیاده سازی کلاس Person بسیار مفصل است و شامل فیلدها و متدهای بسیار زیادی است. اگر بخواهیم دقیقا همین فیلدها و متدها را در کلاس Student کپی کنیم و از وراثت استفاده نکنیم، برنامه بدون هیچ مشکلی اجرا میشود، اما نکته اینجا است که اگر در آینده بخواهیم تغییراتی در کلاس Person ایجاد کنیم و یا یک باگی را پیدا کنیم و بخواهیم آن را رفع کنیم، تک تک کلاسهایی که ویژگیها و متدهای کلاس Person را کپی کردهاند، باید تغییر کنند و این کار بسیار سخت و زمانبر و کلافه کننده است. اما وقتی از وراثت استفاده میکنیم، فقط کافی است که کلاس Person را تغییر دهیم، در این صورت به صورت خودکار تمام زیر کلاسها تغییر میکنند.
رابطهی IS-A
اصطلاحا بین زیر کلاس و اَبَر کلاس رابطهی IS-A برقرار است. به جملهی زیر توجه کنید:
«Java IS A Programming Language»
معنی جملهی بالا میشود: جاوا «است یک» زبان برنامه نویسی. میتوان گفت که Language یا زبان هم یک کلاس است که زیر مجموعههایی را دارد. مثل زبانهای محاورهای، زبانهای برنامه نویسی و موارد دیگر، که جاوا زیر مجموعهای (زیر کلاسی) از زبانهای برنامه نویسی است. بنابراین بین جاوا و زبانهای برنامه نویسی، رابطهی IS-A برقرار است. پس بین کلاسهایی که در جاوا ایجاد میکنیم، این رابطه برقرار است. به عنوان مثال Student IS-A Person. یعنی هر دانش آموز یک شخص هم است یا هر شئی (نمونهای) از کلاس Student، یک شی (نمونه) از کلاس Person نیز است.
نکته: تلفظ IS-A دقیقا به همان صورتی است که در زبان انگلیسی تلفظ میکنیم.
سطح دسترسی protected
تا این قسمت از آموزش با سه سطح دسترسی یعنی: public, private, package access آشنا شدهایم.
public به این معنا بود که به عنوان مثال اگر فیلدی را به صورت public تعریف کنیم، در همهی قسمتهای برنامه به آن فیلد دسترسی داریم. private به این معنا بود که فیلد یا متد مورد نظر، فقط در کلاسی که تعریف شدهاند قابل دسترسی هستند و package access هم فیلدها و متدها فقط در داخل پکیجی که تعریف شدهاند قابل دسترسی هستند. اما در این بین سطح دسترسی دیگری نیز وجود دارد با نام protected. از این سطح دسترسی در مبحث ارث بری استفاده میشود. اجازه دهید یک مثال برای آن بیاوریم.
کلاس Person در پکیج ir.zoomit قرار گرفته شده است و به صورت زیر است:
package ir.zoomit;public class Person { protected String name; int age;}
همانطور که مشاهده میکنید یکی از فیلدهای کلاس به صورت protected است و دیگری به صورت package access. حالا به کلاس Student توجه کنید که از کلاس Person ارث بری کرده است و در داخل همان پکیج ir.zoomit قرار گرفته شده است:
package ir.zoomit;public class Student extends Person { public void show() { System.out.println(age); }}
همانطور که مشاهده میکنید، در کلاس Student میخواهیم مقدار فیلد age را که به صورت package access تعریف شده است در خروجی استاندارد نمایش دهیم. تا این مرحله اگر آبجکتی ایجاد و برنامه را اجرا کنیم، بدون هیچ مشکلی برنامه اجرا میشود و در خروجی نیز عدد صفر چاپ میشود. حالا میخواهیم یک پکیج دیگری با نام مثلا com.google ایجاد کنیم و کلاس Person را در آن قرار دهیم. تصویر زیر:
همانطور که مشاهده میکنید، کلاس Person را به یک پکیج دیگر (com.google) انتقال دادهایم. حالا اگر برنامه را ذخیره کنیم، با خطای کامپایل مواجه میشویم و ارور: The field Person.age is not visible نمایش داده میشود. ارور به ما میگوید که فیلد age نمایان (visible) نیست یا به عبارت دیگر به آن فیلد دسترسی نداریم. حالا در کلاس Student و در متد ()show که پیاده سازی آن نمایش مقدار فیلد age است را تغییر دهید و بجای آن فیلد name را بنویسید و سپس برنامه را Save کنید. در این صورت با هیچ خطایی مواجه نمیشوید و برنامه کامپایل و اجرا میشود. علت چیست؟ علت این است که فیلد name را با سطح دسترسی protected تعریف کردهایم و بنابراین تمام زیر کلاسها (چه در یک پکیج باشند و چه در پکیجهای مختلف)، به فیلدهای کلاس پدر خود دسترسی دارند.
البته اگر یک فیلد به صورت protected تعریف شود و کلاسهای دیگری از آن کلاس که فیلد protected در آن تعریف شده است، ارث بری نکنند، تا زمانی که در داخل یک پکیج باشند، آن فیلد در دسترس همهی کلاسها خواهد بود.
کلیدواژهی super
قبلا با کلیدواژهی this آشنا شدهایم. امروز میخواهیم با کلمهی کلیدی super آشنا شویم که به نوعی نقطهی مقابل this است. فرض کنید در کلاس والد یک فیلدی تعریف کردهاید، مثلا با نام name که از جنس کلاس String است، و دقیقا فیلدی دیگر با همین نام و با همان جنس کلاس در کلاس فرزند تعریف کردهاید. اگر بخواهیم از کلاس فرزند به فیلدی که در کلاس والد تعریف شده دسترسی پیدا کنیم، بایداز کلیدواژهی super استفاده کنیم. به کد زیر توجه کنید:
package ir.zoomit;public class Person { protected String name; int age;}
کلاس بالا، کلاس والد است.
package ir.zoomit;public class Student extends Person { private String name; public void show() { System.out.println(name); }}
کلاس فوق نیز کلاس فرزند است که از کلاس پدر (Person) ارث بری کرده است. توجه کنید که متغیر name در هر دو کلاس تعریف شده است. اگر به صورتی که در کد بالا نوشته شده است (استفادهی از متغیر name بدون به کارگیری super)، جاوا به صورت خودکار متغیر کلاس فرزند و در نظر میگیره. اما قصد داریم که از فیلد کلاس پدر استفاده کنیم. بنابراین از کلیدواژهی super استفاده میکنیم. کد زیر:
package ir.zoomit;public class Student extends Person { private String name; public void show() { System.out.println(super.name); }}
برای تمرین بیشتر در مورد Composition جستجو کنید