الفئات المجردة ( PHP Abstract Classes ) عبارة عن قوالب، وتستخدم للتوريث فقط. أي لا يمكن إنشاء كائنات جديدة منها.
كما نعلم أن مصطلح قالب يطلق على الأشياء ذات الإطار الخارجي فقط والتي تستخدم في بناء شيئ آخر. لذلك فالفئات المجردة عبارة عن قالب عام يستخدم في التوريث وكما قلنا لا يمكن إنشاء كائنات جديدة من الفئات المجردة نفسها ولكن يمكن للفئات الوارثة.
يتم تعريف الفئة المجردة باستخدام الكلمة abstract قبل تعريف الفئة :
<?php abstract class A{ //properties //methods } class B extends A{ //properties //methods } ?>الآن سنقوم ببناء فئة مجردة A وتوريثها للفئة B مع محاولة إنشاء كائن من كلاً منهما
<?php abstract class A{ public function user(){ echo "User"; } } class B extends A{ public function user2(){ $this->user(); echo "2"; } } echo "<p>نتيجة إنشاء كائن من الفئة الوارثة</p><br>"; $obj = new B(); $obj->user2(); echo "<br><p>نتيجة إنشاء كائن من الفئة المجردة</p>"; //محاول غير صحيحة لإنشاء كائن من الفئة المجردة //$Obj2 = new A(); // قم بإزالة علامة التعليق ?>
شرح الكود :
1 – قمنا بتعريف فئة مجردة A باستخدام الكلمة abstract في السطر رقم 2
2 – في السطر رقم 3 قمنا بعمل الطريقة user ومهمتها فقط طباعة الكلمة user
3 – في السطر رقم 7 بدأنا توريث الفئة المجردة A إلى الفئة B
4 – في السطر رقم 8 قمنا بعمل الطريقة user2 ومهمتها استخدام نتيجة الطريقة user لطباعة الكلمة user2.
5 – في السطر رقم 14 قمنا بإنشاء كائن جديد من الفئة B وخصصناه للمتغير obj$
6 – في السطر رقم 15 استدعينا الطريقة user2 والتي ستقوم بطباعة الكلمة user2
7 – حاولنا في السطر رقم 18 إنشاء كائن جديد من الفئة المجردة A . لكن هذه العملية لن تنجح وسيظهر خطأ فادح ( Fetal Error ) ينص على أنه لا يمكن إنشاء كائن جديد من الفئة المجردة ( قد لا يظهر إي شيئ وذلك حسب إعدادات معالجة الأخطاء PHP Error Handling ).
[button title=”عرض نتيجة / تحميل الكود” link=”https://makiomar.com/php-oop/abstract_classes/abstract.php” target=”_blank” size=”” color=”” class=””]
الطرق المجردة Abstract Methods
في الحقيقة أن الهدف الأكثر شيوعاً لاستخدام الفئات المجرده هو أنك تريد بناء فئة ما لها خصائص ووظائف معينه لتوريثها لفئات أخرى لكن أنت على يقين أن كل فئة من هذه الفئات الوارثة ستقوم بتنفيذ الوظيفة بشكل مختلف. وللتحكم في سلوك الفئات الوارثة يتم استخدام الطرق المجرده.
الطريقة المجردة هي طريقة يتم تعريفها بدون كود قابل للتنفيذ ( لايوجد كود بين الاقواس { } ولا حتى الأقواس نفسها) ويتم تعريفها بالكلمة abstract بما أن الفئات المجردة مخصصة للتوريث فلايمكن استخدام الطرق الخاصة ( private methods ). اقرأ عن ( PHP Visibility ) عندما تقوم بتعريف الطرق المجردة داخل الفئة المجردة , فأنت تفرض على كل الفئات الوارثة استخدام هذه الطريقة ، لذا يجب إعادة تعريف الطرق المجردة في الفئات الوارثة ولكن بدون الكلمة abstract ، ويطلق على الفئة الوارثة التي تم إعادة تعريف الفئات المجردة بها بـ Concrete Child Class. إذا كنت تتذكر جيداً تمرير الوسائط بالمرجعيه ( PHP Passing by Reference ) فلابد انك تتذكر ان تغير قيمة المرجع يقوم بتغيير قيمة المتغير الأصلي. كذلك الوضع في الطرق المجردة ، فعند إعادة تعريف الطريقة المجردة مرة أخرى داخل الفئة الوارثة فكأنك قمت بعمل مرجع للطريقة المجرده نفسها وبالتالي أي قيمة ناتجة عن الطريقة المعرفة داخل الفئة الوارثة سيتم تخصيصها للطريقة المجرده. أحياناً تسمى الطرق المجردة بـ Method Signature OR Method Stud.
مثال :
<?php abstract class A{ abstract public function test(); abstract public function test2(); //abstract private function test3() } class B extends A{ public function test(){ echo "User1"; } public function test2(){ echo "User2"; } } $obj = new B(); $obj->test(); echo "<br>"; $obj->test2(); ?>كما نلاحظ تعريف الطرق المجردة في الأسطر 3 , 4 ولكن في السطر رقم 5 تم تعريف الطريقة على أنها خاصة private وهذا لا يصلح لذلك حولناها إلى تعليق ( PHP Comment ) حتى لا يظهر خطأ. ثم قمنا بتوريث الفئة A إلى B مع إعادة تعريف الدوال المجردة مرة أخرى ولكن بدون الكلمة abstract.
[button title=”عر نتيجة / تحميل الكود” link=”https://makiomar.com/php-oop/abstract_classes/abstract_methods.php” target=”_blank”size=””color=”” class=””]
لكي تستطيع فهم الفئات المجرده بشكل أعمق والفائده منها ، تخيل أنك تعمل داخل مصنع لتصنيع الروبوت ، بحيث تنقسم أنواى الروبوت إلى:
روبوت منزلي روبوت للصناعات روبوت حربيوهناك خصائص ووظائف مشتركة تجمع هذه الثلاثة أنواع للروبوت وهي :
1- الخصائص
له يدين له رجلين له راس له عينين لونه أبيض2 – الطرق / السلوك
يستطيع المشي يستطيع الجري يستطيع الجلوس يستطيع الوقوف يتحرك يميناً يتحرك يساراً يتحرك للخلف يتحرك للأماملكن قد يختلف كل منهم في طريقة تنفيذ الوظائف :
الروبوت المنزلي ينفذ حركاته بسرعة معتدلة تتناسب مع هدوء المنزل. روبوت الصناعات ينفذ الحركات بشكل أسرع نسبياً حتى يتناسب مع سرعة الإنتاج. الروبوت الحربي سيكون أكثرهم سرعة للتنقل بين الأماكن بشكل أسرع في وقت الحرب.في هذه الحالة تقوم ببناء فئة يتم تعريف الخصائص والطرق ( السلوك ) المشتركة مع اضافة معلوماتها داخل الفئة أما السلوك الذي يتم تنفيذه بشكل مختلف حسب كل فئة يجب تعريفه بشكل مجرد من المعلومات بحيث يتم تحديد طريقة آداء السلوك حسب كل فئة. وإذا كنا سنحاكي ذلك على شكل برنامجي يمكننا تخيله كالآتي :
<?php abstract class robot{ public $hands; public $legs; public $head; public $eye; public $color; public function walk(){ echo "I can walk"; } public function run(){ echo "I can run"; } public function set(){ echo "I can set"; } public function stand(){ echo "I can stand"; } public function moveRight(){ echo "I can move right"; } public function moveLeft(){ echo "I can move left"; } public function backward(){ echo "I can move backward"; } public function forward(){ echo "I can move forward"; } public abstract function speed(); } class homeRobot extends robot{ public function speed(){ return "I'm a home robot and i move slowly"; } } class factoryRobot extends robot{ public function speed(){ return "I'm a factory robot and i move fast"; } } class warRobot extends robot{ public function speed(){ return "I'm a war robot and i move very fast"; } } $homerobot= new homeRobot(); echo $homerobot->speed().'<br>'; $factoryrobot= new factoryRobot(); echo $factoryrobot->speed().'<br>'; $warrobot= new warRobot(); echo $warrobot->speed(); ?>لاحظ تم تعريف جميع الخصائص والطرق المشتركة بشكل طبيعي، أما عند تعريف الطريقة التي ستحدد سرعة كل فئة بشكل مختلف حسب نوعها ، تم تعريفها بشكل مجرد في السطر رقم 32 وهي ()speed. ثم بعد ذلك قمنا بتوريث الفئة المجردة robot مع تحديد السلوك المخصص لكل فئة حسب نوعها مثل ما حدث في الاسطر 36 ، 41 و 46.
[button title=”عرض نتيجة / تحميل الكود” link=”https://makiomar.com/php-oop/abstract_classes/abstract-class-simulation.php” target=”_blank” size=”” color=”” class=””]
خصائص الفئات المجردة1 – الفئة المجردة يمكن أن نسميها عقد ( Contract ) بحيث من يكتب هذا العقد يمكنه أن يفرض سلوك ما على الطرف الآخر ويستطيع إجباره على الطريقة أو الشكل الذي سيؤدي به هذا السلوك مع امكانية اضافة تعديل بسيط على صيغة العقد (تمرير وسائط اضافية لها قيمة عن اعادة تعريف الطرق في الفئات الوارثة). مع العلم أن هذا العقد يتم بين الفئات التي لها علاقة ببعض، لأنه بالطبع الفئات المجرده تستخدم للتوريت لذلك الفئات المرتبطة ببعض. (هذا يعتبر من الفوارق الواضحة بين الفئات المجرده والواجهات PHP Interfaces التي يمكن تطبيقها على فئات ليس لها علاقة ببعض)
<?php abstract class MotorVehicl { public $fuel; //جميع المركبات تعمل بالوقود فهذه الطريقة لاتحتاج لإعادة تعريف في الفئات الوارثة public function getFuel(){ return $this->fuel; } //لكن هذه الطريقة يمكن ان تختلف من مركبة لأخرى abstract public function run(); } class Car extends MotorVehicl { //استخدام إلزامي للطريقة المجردة public function run(){ return "Wrooooooooom"; } } $car = new Car(); $car->fuel= "45litres"; echo $car->getFuel(); echo '<br>'; echo $car->run(); ?>2 – يمكن استخدام الثوابت ( PHP Constants ) عند تعريف الخصائص ( المتغيرات ).
3 – يمكن أن تحتوي على طرق مجردة ( Abstract Methods ) أو طرق كاملة ( Complete Methods ).
4 – يمكن استخدام أي محدد من محددات الرؤية ( Public – Protected ) ولا يمكن استخدام ( private ). لأن العناصر الخاصة لا يمكن توريثها ، في حين الفئة المجردة ذاتها تستخدم للتوريث فقط. إذا مالفائدة في الأساس من استخدام Private مادمت لن تقوم باستخدام الفئة المجردة في حد ذاتها.
5 – عند التوريث ( PHP Inheritance ) لا يمكن استخدام خصائص / طرق الفئة الوارثة ( الإبن) في الفئة الموروثة ( الأب )، لكن عند استخدام الطرق المجردة، فأنت بالأساس تقوم بتعريف الطريقة المجردة في الفئة الموروثة ( الأب ) بدون كود لكي تعيد استخدامها مرة أخرى في الفئة الوارثة ( الإبن ) وبالتالي عند تنفيذ كود الطريقة التي تم اعادة تعريفها في الفئة الوارثة فإن النتيجة تم تخصيصها للطريقة سواء في الفئة الوارثة أو الموروثة وهذا ما ذكرناه في النقطة رقم 4 عند بداية تعريف الطرق المجرده. لاحظ الكود التالي
<?php abstract class Animal { function greating(){ $sound = $this->sound(); //موجودة في الفئة الوارثة return strtoupper ($sound); } abstract function sound();// هذا هو العقد } class Dog extends Animal { //استخدام إلزامي للطريقة المجردة public function sound(){ return "WooF!"; } } class Cat extends Animal { //استخدام إلزامي للطريقة المجردة public function sound(){ return "Meow!"; } } $dog = new Dog(); echo $dog->greating(); echo '<br>'; $cat = new Cat(); echo $cat->greating(); ?>هل قرأت النقطة رقم 4 عن تعريف الطرق المجرده؟ إذا كانت إجابتك تعم إذاً :
فكما يظهر في السطر رقم 5 فقد تم تخصيص نتيجة الطريقة ()sound التي تم تعريفها في السطر رقم 8 للمتغير sound$ مع العلم أن هذه الطريقة لا تحتوي على أي كود ، وبالتالي لن يكون هناك نتيجة للطريقة. لكن ما يحدث هنا أننا نقوم في السطر رقم 11 بتوريث الفئة المجردة Animal إلى الفئة Dog ، ثم بدأنا بتعريف الدالة المجردة ()sound مرة أخري مع إضافة كود يقوم بطباعة صوت الكلب “!WooF” في السطر رقم 15 . بالتالي ستؤثر هذه الطريقة على الطريقة المجرده التي تم تعرفها في السطر رقم 8 . لذلك سيتم تخصيص النتيجة “!WooF” للمتغير sound$ وهو ما تم تعريفه في الفئة الموروثة ( الأب ) في السطر رقم 5 سابقاً. كذلك الأمر بالنسبة للفئة Cat في السطرر قم 18 .
[button title=”عرض نتيجة / تحميل الكود” link=”https://makiomar.com/php-oop/abstract_classes/abstract_methods_2.php” target=”_blank” size=”” color=”” class=””]
6 – إذا كانت الفئة A فئة مجردة وتم توريثها لفئة أخرى مجردة B فلايجب أن تحتوي الفئة المجردة B في هذه الحالة على الطرق المجردة التي تم تعريفها في الفئة A.
7 – نعرف أنه عند تعريف الطرق المجردة داخل الفئة الوارثة ( الإبن ) يجب أن يكون لها نفس حدود الرؤية أو أقل. مثلاً إذا تم تعريف طريقة ما داخل فئة موروثة ( أب ) على أنها protected ، فيمكن تعريفها في الفئة الوارثة ( إبن ) إما protected أو public . أما إن كانت معرفة في الفئة المروثة على أنها public فلايمكن تعريفها إلا public في الفئة الوارثة.
8 – إذا كانت الفئة C ترث الفئة B فيجب أن تحتوي على الطرق المجردة التي تم تعريفها في الفئتين B,A .
9 – يمكن تمرير الوسائط أثناء تعريف الطرق المجردة في الفئة المجردة، وعند توريث الفئة المجردة يجب إعادة تعريف الطريقة المجردة بنفس الوسائط أو يمكنك إضافة وسائط أخرى لكن بشرط تخصيص قيم افتراضية للوسائط الجديدة، وإلا سيظهر خطأ . لاحظ الكود التالي :
<?php abstract class AbstractClass { // تعريف طريقة مجردة مع تمرير وسيط لها abstract protected function prefixName($name); } class ConcreteClass extends AbstractClass { // اضافة وسيط جديد له قيمة افتراضية لوسائط الدالة المجردة public function prefixName($name, $separator = ".") { if ($name == "Ahmed") { $prefix = "Mr"; } elseif ($name == "Mariam") { $prefix = "Mrs"; } else { $prefix = ""; } return "{$prefix}{$separator} {$name}"; } } $class = new ConcreteClass; echo $class->prefixName("Ahmed")."<br>"; echo $class->prefixName("Mariam"); ?>في السطر رقم 5 قمنا بتعريف الطريقة المجردة ()prefixName مع تمرير الوسيط name$ ، ثم قمنا بتوريث الفئة مع إعادة تعريف الطريقة مرة أخرى في السطر رقم 10 مع إضافة وسيط جديد separator$ له قيمة افتراضية “.” . ثم أنشأنا كائن جديد بشكل طبيعي واستدعينا الطريقة ()prefixName في السطرين 22، 23 وستظهر النتائج بدون مشاكل. أما إذا حاولت إزالة القيم الإفتراضية للوسيط separator$ فسيظهر خطأ فادح ( Fetal error ).
[button title=”عرض نتيجة / تحميل الكود” link=”https://makiomar.com/php-oop/abstract_classes/passing_args_abstract_method.php” target=”_blank” size=”” color=”” class=””]


