کلاس ها و متدهای انتزاعی: نگاهی عمیق تر به جزئیات

درسنامه درس 8 از سری شی گرایی در PHP
php-oop-deep-abstract-class

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

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

abstract class Ch2_Product
{

// خصوصیات در این قسمت تعریف شده اند
protected $_type;
protected $_title;

// متدها در این قسمت تعریف شده اند
public function getProductType()
{
return $this->_type;
}
public function getTitle()
{
return $this->_title;
}
abstract public function display();

}

اگر به جلسه ی قبل سری نزده اید، حتما قبل از ادامه ی این جلسه به عقب برگردید و مطالعه ی سریعی داشته باشید. در این جلسه به مثال همیشگی ماشین خود بر میگردیم.

آیا یک کلاس انتزاعی می تواند متدهای غیر انتزاعی داشته باشد؟

بله، کلاس های انتزاعی می توانند متدهای غیر انتزاعی داشته باشند. این کلاس ها حتی می توانند property (خصوصیت) داشته باشند و می دانیم که

خصوصیت ها نمی توانند abstract (انتزاعی) باشند

به مثال زیر توجه کنید:

abstract class Car {
  // کلاس های انتزاعی می توانند خصوصیات مختلفی داشته باشند
  protected $tankVolume;
 
  // کلاس های انتزاعی میتوانند متدهای غیر انتزاعی داشته باشند
  public function setTankVolume($volume)
  {
    $this -> tankVolume = $volume;
  }
 
  // متد انتزاعی
  abstract public function calcNumMilesOnFullTank();
}

در مثال بالا ابتدا یک خصوصیت به نام tankVolume$ ایجاد کرده ایم و آن را از نوع protected قرار داده ایم. این عبارت در فارسی به معنای "حجم باک" است. اگر مواردی مانند protected، private و public را از یاد برده اید به مقاله زیر مراجعه کنید:

سپس متد setTankVolume را (که نوعی setter است) تعریف کرده و در آخر نیز یک متد انتزاعی به آن افزوده ایم. این کد به راحتی کار میکند و هیچ نقصی ندارد؛ به این ترتیب ثابت می شود که کلاس های انتزاعی میتوانند متدهای غیرانتزاعی داشته باشند.

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

شاید تا اینجای کار برای شما سوال پیش آمده باشد که آیا ساخت فرزند برای کلاس های انتزاعی با ساخت فرزند برای کلاس های عادی تفاوتی دارد؟ بله تفاوتی وجود دارد اما تفاوت بزرگی نیست.

ساخت کلاس فرزند برای کلاس های انتزاعی مانند کلاس های عادی با کلید واژه ی extends صورت می گیرد و تنها تفاوت آن در این است که کلاس های فرزند موظف هستند تا برای متدهای انتزاعی کد بنویسند. اگر یادتان باشد در جلسه ی قبل گفتیم که متدهای انتزاعی هیچ بدنه ای (کدی) ندارند و تنها نام و پارامتر میگیرند. وظیفه ی نوشتن کد های داخل این متدها بر عهده ی تک تک کلاس های فرزند است.

اگر بخواهیم در مثال بالا (calcNumMilesOnFullTank) یک کلاس فرزند ایجاد کنیم، کد آن به شکل زیر خواهد بود:

class Honda extends Car {

  // تعریف بدنه برای متد انتزاعی
  public function calcNumMilesOnFullTank()
  {
    $miles = $this -> tankVolume*30;
    return $miles;
  }
}

از آن جایی که کلاس Honda فرزند کلاس Car محسوب می شود بنابراین کد های آن را هم به ارث می برد. مهم ترین قطعه کدی که به ارث می برد نیز همان متد انتزاعیِ calcNumMilesOnFullTank است و بر اساس مطالبی که مرور کردیم این کلاس باید برای متد calcNumMilesOnFullTank بدنه بنویسد.

ما در این نمونه کد فرض را بر این گذاشته ایم که فرمول میزان مصرف سوخت برای یک هوندا معادل است با حجم باک ضربدر 30. این تنها یک فرض غیر واقعی برای حل مسئله است و نیازی نیست خودتان را درگیر ماشین ها و فرمول های ریاضی کنید!

سپس یک کلاس فرزند دیگر تعریف میکنیم و نام آن را Toyota می گذاریم. فرقی نمی کند دفعه ی چندم باشد، همین که این کلاس فرزند کلاس Car است، باید برای calcNumMilesOnFullTank بدنه بنویسد:

class Toyota extends Car {
  // نوشتن بدنه برای متد انتزاعی
  public function calcNumMilesOnFullTank()
  {
    return $miles = $this -> tankVolume*33;
  }
 
  public function getColor()
  {
    return "beige";
  }
}

اگر دقت کنید علاوه بر نوشتن بدنه برای متد انتزاعی، یک متد اختصاصی نیز برای خودش نوشته ایم. این متد که getColor نام دارد رنگ ماشین را به ما برمیگرداند که در این مثال "بژ" است.

برای تمرین آخر این دو مثال را ادغام می کنیم و شیء ای به نام Toyota1 میسازیم و حجم باک آن را 10 لیتر در نظر میگیریم.

$toyota1 = new Toyota();
$toyota1 -> setTankVolume(10);
echo $toyota1 -> calcNumMilesOnFullTank();// 330را بر میگرداند  
echo $toyota1 -> getColor();//خواهد بود beige کلمه ی برگشتی

خروجی کد calcNumMilesOnFullTank همانطور که در خود کد به صورت کامنت آورده شده است 330 است و رنگ ماشین هم که از قبل بژ تعیین شده بود.

نکته: لزومی ندارد که کلاس های انتزاعی دارای متدهای انتزاعی باشند اما اگر در کلاسی متد انتزاعی وجود داشته باشد باید حتما کلاس هم انتزاعی تعیین شود.

کاری که برای کلاس Car و فرزندانش انجام دادیم می توانیم برای مثال فروشگاهمان نیز انجام دهیم. کد اولیه ی فروشگاه ما به این شکل بود:

abstract class Ch2_Product
{

// خصوصیات در این قسمت تعریف شده اند
protected $_type;
protected $_title;

// متدها در این قسمت تعریف شده اند
public function getProductType()
{
return $this->_type;
}
public function getTitle()
{
return $this->_title;
}
abstract public function display();

}

حالا باید برای تابع display بدنه بنویسیم. ابتدا این کار را برای کلاس کتاب (Book) انجام می دهیم:

public function display()
{
echo "<p>Book: $this->_title ($this->_pageCount pages)</p>";
}

سپس برای کلاس DVD یا Multimedia و یا هر اسمی که شما برایش انتخاب کرده باشید:

public function display()
{
echo "<p>DVD: $this->_title ($this->_duration)</p>";
}

همچنین می توانید عین کد های تست و نتیجه گیری که برای کلاس Car نوشته ایم، برای کلاس محصولات یا Product بنویسید. اگر نمیدانید کلاس Product چیست به قسمت قبلی سری بزنید. در این قسمت، مثالی از یک فروشگاه زدیم و اسم کلاس پدر را Product گذاشتیم.

برای جمع بندی نهایی یک مثال کامل از این مسئله را خدمت شما ارائه می کنیم:

<?php
abstract class AbstractClass
{
    abstract protected function prefixName($name);

}

class ConcreteClass extends AbstractClass
{

    public function prefixName($name, $separator = ".") {
        if ($name == "Pacman") {
            $prefix = "Mr";
        } elseif ($name == "Pacwoman") {
            $prefix = "Mrs";
        } else {
            $prefix = "";
        }
        return "{$prefix}{$separator} {$name}";
    }
}

$class = new ConcreteClass;
echo $class->prefixName("Pacman"), "\n";
echo $class->prefixName("Pacwoman"), "\n";
?>

خروجی کد بالا عبارت های زیر خواهند بود:

Mr. Pacman
Mrs. Pacwoman

نکاتی در مورد کد بالا:

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

1- بدنه (در انگلیسی Body) در در کد ها یعنی یک بلوک از کد ها. به طور مثال یک تابع به شکل زیر را در نظر بگیرید:

function writeMsg() {
    echo "Hello world!";
}

قسمتی که مساوی با (;"!echo "Hello world) است بدنه ی این تابع است؛ یعنی یک گروه کد از یک خط تا 1000 خط. وقتی می گوییم بدنه نویسی منظومان این است که کد های داخل متد (که یک تابع است) را برایش بنویسیم. چرا؟ چون متد انتزاعی است و هیچ بدنه یا کدی ندارد. به قست قبلی مراجعه کنید، در این رابطه صحبت کرده ایم.

2- پارامتر (parameter) یا آرگومان (argument) همان مقادیری هستند که در پرانتز های جلوی نام تابع قرار می گیرند اما یک تفاوت دارند:

  • پارامتر به متغیری گفته می شود که در زمان تعریف تابع در پرانتز های جلوی نام تابع قرار میگیرد. مانند:
function familyName($fname) {
    echo "$fname Refsnes.<br>";
}

در اینجا fname$ یک پارامتر است.

  • آرگومان به مقدار یا متغیری می گویند که هنگام صدا زدن تابع در پرانتز های جلوی نام آن تابع قرار میگیرد. مانند:
familyName("Kai Jim");

در اینجا "Kai Jim" یک آرگومان است.

امیدوارم از این قسمت استفاده ی کافی را برده باشید. در قسمت بعد سراغ مباحث مختلفی از جمله interface ها می رویم که به کلاس های انتزاعی بسیار شبیه هستند.

تا قسمت بعد یا حق.

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

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

mehrzad
20 شهریور 1402
سلام ممنون واقعا همه سری آموزش های روکسو عالیه یک سوال داشتم اینکه ما نمیتونیم abstract properties داشته باشیم برای مثال : abstract public $name

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

محسن
15 آذر 1400
سلام خسته نباشید تشکر از سایت مفیدتون در مثال آخریتون که خروجی کد (Mr. Pacman Mrs. Pacwoman) بود اگه در قسمت شی برنامه مقداری برای کلاس فرزن تعریف کردیم اگه بجای اسم pacman هر اسم دیگه ای بزاریم یا در قسمت pacwoman اسم دیگری تعریف کنیم بازهم کلاس فرزند عمل میکنه و خروجی برابر mrs میشه با این وجود چرا کد کار میکنه ؟ و قسمت else آخر کار نمیکنه؟

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

محسن
18 فروردین 1399
ببخشید معنی \n در آخر سطر24و25 متوجه نشدم. توضیح میدین؟ چون روی لپ تاپ من با مرورگر کروم و ویندوز 10 تاثیری ندیدم و نتیجه هردو سطر هم در یک خط نمایش داده میشه

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

امیر زوارمی
27 فروردین 1399
سلام دوست عزیز کاراکتر های خاصی مثل n\ یا r\n\ در سیستم عامل های مختلف به صورت مختلفی پردازش میشه و کارش ایجاد line break هست (رفتن به خط بعد). n\ معمولا روی ویندوز جواب نمیده و اگه اینطور بود به جاش از PHP_EOL استفاده کنید. همچنین اگر منظورتون اعمال این کاراکتر ها توی مرورگر هست، باید بگم مرورگر شما از HTML استفاده می کنه بنابراین رفتن به خط بعد براش کامل فرق داره. PHP_EOL یا n\ در مرورگر اعمال میشه اما فقط در قسمت سورس کد. برای نمایش یا باید از استفاده کنید یا از تابع nl2br در PHP: https://www.php.net/manual/en/function.nl2br.php

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

مسعود
12 مهر 1398
سلام در نکته کادر آبی رنگ التزامی نه انتزاعی

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

امیر زوارمی
14 مهر 1398
سلام دوست عزیز، از تذکر شما ممنونم. به زودی اصلاح میشه.

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

سهیل محمد بیگی
26 شهریور 1398
با سلام خدمت شما دوست عزیز ممنون از اموزش های بسیار خوبتون شما فرمودین برای کلاس های انتزاعی بدنه تعریف نمیکنیم فقط اسم متد و پارامتر رو تعریف میکنیم و وظیفه ی نوشتن کدهای داخل متدها بر عهده ی تک تک کلاس های فرزند هست اما اینجا چرا تعریف کردین ؟ (منظورم return ایی که داخل بدنش نوشتین) public function getProductType() { return $this->_type; } public function getTitle() { return $this->_title; } با تشکر

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

امیر زوارمی
26 شهریور 1398
سلام دوست عزیز، ما براتون نوشتیم که: اگر یادتان باشد در جلسه ی قبل گفتیم که متدهای انتزاعی هیچ بدنه ای (کدی) ندارند و تنها نام و پارامتر میگیرند. وظیفه ی نوشتن کد های داخل این متدها بر عهده ی تک تک کلاس های فرزند است. بنابراین نگفتیم کلاس های انتزاعی بدنه ندارن، بلکه متد های انتزاعی هستن که بدنه ندارن. این کدی رو هم که آوردین اگه دقت کنید اصلا انتزاعی نیست. ما متد های انتزاعی رو با کلیدواژه ی abstract مشخص میکنیم اما متد هایی که توی کد شما هست public function هست که یعنی اصلا متد انتزاعی نیستن، بلکه متد عادی هستن (حالا توی کلاس انتزاعی باشن یا نه یه مسئله ی دیگه است).

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

سهیل محمد بیگی
27 شهریور 1398
با سلام بله درست میفرمایید منظورم این بود که متد ها در حالت انتزاعی بدنه ندارن ، و شما هم به این نکته اشاره کردین کدی که کپی کردم اولین (کد مثال) همین صفحه هست (چون شلوغ نشه من قسمت abstract نذاشتم) حالا سوال اینجاست که الان داخل متد ()getProductType کد : return $this->_type; قرار گرفته ، منظورم اینه که در حالت abstract داخل بدنش چیزی گذاشتیم که و اون کد همین return $this->_type; است قرار بود نباشه که ممنون از راهنماییتون

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

امیر زوارمی
28 شهریور 1398
شما متوجه منظور بنده نشدید فکر کنم... من سعی میکنم دوباره بگم تا بهتر متوجه بشین. متد هایی که داخل کلاس های انتزاعی باشن، متد انتزاعی نیستن. فقط و فقط متد هایی انتزاعی هستن که حتما اولشون (وقتی داریم تعریفشون میکنیم) کلیدواژه ی abstract داشته باشن. شما کد رو از اولین مثال همین صفحه کپی کردید درسته؟ خب توی این مثال کلاسمون انتزاعی هست چون گفتیم abstract class Ch2_Product اما متد های داخلش اصلا انتزاعی نیستن به جز متد آخری که کلیدواژه ی abstract رو اولش داره: abstract public function display(); به زبون ساده تر متد display از نوع انتزاعی هست که چیزی داخلش نیست، اما متد های getProductType و getTitle انتزاعی نیستن بنابراین داخلشون کد نوشته شده (همون دستور return). یک مثال از وب سایت رسمی PHP: abstract class AbstractClass { // Force Extending class to define this method abstract protected function getValue(); abstract protected function prefixValue($prefix); // Common method public function printOut() { print $this->getValue() . "\n"; } } لینک مثال: https://www.php.net/manual/en/language.oop5.abstract.php این مثال یک کلاس انتزاعی هست که داخلش دو متد انتزاعی وجود داره (getValue و prefixValue) و یک متد عادی و غیر انتزاعی (printOut). به صفحه ی رسمی کلاس ها توی لینک بالا مراجعه کنین، با خوندن مثال ها متوجه میشین.

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

سهیل
20 مهر 1398
بله درست میفرمایید ببخشید من خوب توجه نکردم خیلی ممنون

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

کاوه
17 شهریور 1398
Kai jim یک آرگومان است

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

امیر زوارمی
17 شهریور 1398
بله درست میفرمایید اشتباه تایپی بوده. بابت یادآوری شما ممنونم، در اسرع وقت اصلاح خواهد شد.

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