روابط بین کلاس پدر و فرزند، Override و final

درسنامه درس 6 از سری شی گرایی در PHP
php-oop-override-final

با سلام و عرض ادب خدمت شما خواننده ی گرامی، با قسمت ششم از سری مباحث آموزش برنامه نویسی شیء گرای PHP در خدمت شما هستیم. در قسمت قبلی وارد مبحث وراثت یا inheritance شدیم و مفهوم کلی وراثت در برنامه نویسی شیء گرا را توضیح دادیم. در این قسمت نیز مطالب بیشتری در مورد وراثت خواهیم آموخت. از آن جهت که آموزش ها به صورت عملی و در عین حال مفید و کوتاه باشند مفاهیم مبحث وراثت را به صورت سوال و جواب طی خواهیم کرد! در ادامه متوجه خواهید شد.

استقلال کدها در کلاس های فرزند

آیا یک کلاس فرزند می‌تواند در عین حالی که از کدهای کلاس پدر استفاده می کند، خصوصیات و متدهای مخصوص خودش را داشته باشد؟ جواب مثبت است اما این ارتباط یک طرفه است!

این مسئله بدین معنی است که کلاس های فرزند می توانند از کدهای کلاس پدر استفاده کنند اما کلاس پدر نمی تواند از کدهای کلاس فرزند استفاده کند.

اگر این سری آموزشی را دنبال می کنید باید با مثال معروف ماشین در دوره ی ما آشنایی داشته باشید! در این قسمت برای اثبات استقلال کلاس های فرزند میتوانیم از همان مثال ماشین استفاده کنیم:

// کلاس پدر خصوصیات و متدهای خود را دارد
class Car {
  
  //یک خصوصیت خصوص تنها از طریق خود پدر قابل دسترسی است
  private $model;
  
  // خصوصیات و متدهای عمومی میتوانند توسط پدر یا فرزند استفاده شوند
  public function setModel($model)
  {
    $this -> model = $model;
  }
   
  public function getModel()
  {
    return $this -> model;
  }
}
 
  
// کلاس های فرزند میتوانند علاوه بر کدهایی که از پدر به ارث برده اند می توانند کدهای خودشان را نیز داشته باشند
class SportsCar extends Car{
 
  private $style = 'fast and furious';
 
  public function driveItWithStyle()
  {
    return 'Drive a '  . $this -> getModel() . ' <i>' . $this -> style . '</i>';
  }
}
 
 
// یک نمونه از کلاس فرزند میسازیم
$sportsCar1 = new SportsCar();
   
// از متدی استفاده میکنیم که کلاس فرزند از کلاس پدر به ارث برده است
$sportsCar1 -> setModel('Ferrari');
  
// از متدی استفاده میکنیم که مستقیما به کلاس فرزند اضافه شده بود
echo $sportsCar1 -> driveItWithStyle();

نکات مربوط به کد شماره 1:

  • با نگاه به کد ;private $model متوجه می شوید که خصوصیت model یک خصوصیت private (خصوصی) است که تنها توسط کلاس دارنده ی آن قابل استفاده است. با استفاده از یک setter به نام setModel، مقدار این خصوصیت را تعیین و برابر با Ferrari قرار دادیم. اگر در رابطه با setter ها و getter ها چیزی نمی دانید به قسمت های قبلی مراجعه کنید: برنامه نویسی شی گرا در PHP – مفاهیم Setter ،Getter و Constructor
  • سپس خصوصیتی به نام style (به معنی شیوه یا همان استایل) تعریف کردیم و مقدار آن را برابر با fast and furious (به معنی سریع و غضبناک) قرار دادیم.
  • حالا از تابع ()driveItWithStyle که مخصوص کلاس فرزند بود، استفاده می کنیم و به این ترتیب ثابت می شود که کلاس های فرزند می توانند کدهای مستقلی داشته باشند و در صورت استفاده از آن ها با خطا مواجه نخواهیم شد.
  • خروجی کد بالا عبارت زیر خواهد بود:

Drive a Ferrari fast and furious.

سوال: من چنین کدی نوشته ام اما با خطایی مواجه می شوم. چطور ممکن است؟

پاسخ: تصور کنید کد بالا (کد شماره 1)، به صورت زیر نوشته می شد:

// کلاس پدر
class Car {
  //خصوصیت مدل ماشین یک خصوصیت خصوصی است بنابراین فقط از داخل خود کلاس قابل دسترسی است
  private $model;
  
  //عمومی setter متد 
  public function setModel($model)
  {
    $this -> model = $model;
  }
}
  
   
// کلاس فرزند
class SportsCar extends Car{
  //سعی میکند یک ویژگی خصوصی که متعلق به کلاس پدر است را دریافت کند
  public function hello()
  {
    return "beep! I am a <i>" . $this -> model . "</i><br />";
  }
}
   
//ساخت نمونه از کلاس فرزند
$sportsCar1 = new SportsCar();
  
//تعیین مدل ماشین
$sportsCar1 -> setModel('Mercedes Benz');
   
//دریافت یا خروجی گرفتن از مدل ماشین
echo $sportsCar1 -> hello();

خروجی کد ما در این صورت، خطای زیر خواهد بود:

Notice: Undefined property: SportsCar::$model

این یکی از رایج ترین اشتباهات برنامه نویسان مبتدی است؛ توجه کنید که این دو کد منطق کاملا متفاوتی دارند! در این مثال ما setter داریم اما getter نداریم. از طرفی در کدِ تابعِ hello در کلاس فرزند نوشته ایم:

public function hello()
  {
    return "beep! I am a <i>" . $this -> model . "</i><br />";
  }

در واقع به خاطر استفاده ی مستقیم از this$ به جای استفاده از getter، زبان PHP تصور میکند که ما میگوییم به دنبال خصوصیت model درونِ کلاس sportsCar (کلاس فرزند) بگرد. چنین خصوصیتی در کلاس فرزند وجود ندارد (در کلاس پدر تعریف شده است) و از همین جهت به خطای مذکور برمیخوریم. ترجمه ی این خطا برابر است با "اخطار: خصوصیت تعریف نشده: ماشین‌اسپرت::مدل". در واقع به ما میگوید که چنین خصوصیتی وجود ندارد!

سوال: اگر نخواهیم از getter استفاده کنیم چطور؟

پاسخ: راه حل ساده ی شما این است که خصوصیت model را از private به protected تغییر دهید! به همین سادگی! در این حالت خروجی ما صحیح و سالم و برابر عبارت زیر خواهد بود:

beep! I am a Mercedes Benz

باطل کردن (override) خصوصیات و متدهای کلاس پدر

به کد زیر (کد شماره 3) توجه کنید:

// نکته 1
class Car {
  public function hello()
  {
    return "beep";
  }
}
 
//نکته 2
class SportsCar extends Car {
  public function hello()
  {
    return "Hallo";
  }
}
    
//ساخت شیء جدید
$sportsCar1 = new SportsCar();
  
//hello دریافت خروجی از تابع
echo $sportsCar1 -> hello();

نکات کد شماره بالا (کد شماره 3):

  • مرحله 1: در کلاس پدر یک متد با نام hello تعریف میکنیم که مقدار beep را به ما بر میگرداند.
  • مرحله 2: در کلاس فرزند یک متد با نام hello تعریف میکنیم که مقدار Hallo را به ما بر میگرداند.

سوال: زمانی که درخواست خروجی از تابع hello میکنیم کدام خروجی به ما نمایش داده می شود؟

بدون اینکه جواب سوال را بخوانید سعی کنید خودتان آن را جواب بدهید.

پاسخ ساده است؛ Hallo خروجی ما خواهد بود اما چرا؟ اگر ما تابع hello را در کلاس فرزند تعریف نمیکردیم، در هنگام دریافت خروجی از تابع hello که در کلاس پدر نوشته شده بود استفاده می شد (کلاس فرزند کدهای کلاس پدر را به ارث می برد). حالا که این تابع را دوباره در کلاس فرزند تعریف کردیم، مقدار آن override می شود؛ یعنی مقدار ثانویه جایگزین مقدار اولیه می شود.

بر همین اساس می توان فرآیند override شدن را این گونه تعریف کرد: زمانی که یک خصوصیت یا متد را override میکنیم، خصوصیت یا متدی را که در کلاس پدر وجود دارد دوباره در کلاس فرزند تعریف میکنیم، اما با مقادیر متفاوت!

عدم اجازه و دسترسی برای override

اگر شما دوست ندارید که کلاس های فرزند بتوانند خصوصیات و یا متدهای کلاس پدر را override کنند میتوانید از کلید واژه ی final (به معنی "نهایی") استفاده کنید. به مثال زیر توجه کنید:

// را برمیگداند beep کلاس پدر متد عمومی دارد که 
 
class Car {
  final public function hello()
  {
    return "beep";
  }
}
 
//کلاس فرزند متدی دارد که میخواهد متد کلاس پدر را باطل کند
class SportsCar extends Car {
  public function hello()
  {
    return "Hallo";
  }
}
  
 
//ساخت شیء جدید
$sportsCar1 = new SportsCar();
  
//hello دریافت خروجی متد
echo $sportsCar1 -> hello();

توضیح کد بالا (کد شماره 4):

از آنجایی که در متد ()hello در کلاس پدر از کلید واژه ی final استفاده شده است خروجی کد ما به صورت زیر خواهد بود:

Fatal error: Cannot override final method Car::hello()

این خطا به ما می گوید:

خطا از نوع Fatal: شما نمیتوانید متد hello در کلاس car را که دارای کلید واژه ی final است، تغییر داده یا override کنید.

نکات پایانی در مورد کلید واژه ی final

سه نکته ی مهم در رابطه با کلید واژه ی final وجود دارد که برنامه نویسان معمولا در رابطه با آنها دچار سردرگمی می شوند:

  • تعیین یک کلاس (و نه متد) به عنوان final، اعلام میکند که این کلاس نمیتواند کلاس های فرزند داشته باشد و آخرین کلاس در خاندان خود است.
  • کلید واژه ی final تنها برای متدها و کلاس ها استفاده می شوند و یک خصوصیت (property) نمی تواند final باشد.
  • نباید کلید واژه ی final را با private اشتباه بگیرید؛ هیچ کلاس دیگری به جز کلاس پدر به یک متد private دسترسی ندارد اما کلاس های فرزند میتوانند به متد final دسترسی داشته باشند و فقط نمی توانند آن را تغییر بدهند.

برای کسب اطلاعات بیشتر به صفحه ی رسمی PHP در مورد کلید واژه ی final مراجعه کنید.

امیدوارم از این قسمت از سری آموزشی برنامه نویسی شیء گرا PHP استفاده ی کامل را برده باشید.

تا قسمت بعدی در پناه حق.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری شی گرایی در PHP توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما (1 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

رضا
18 مهر 1398
آموزش خوب و روانیه. ممنون.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.