تعدد الأشكال عبر الـ Interfaces
جزء من قسم Object Oriented Programming في رحلة PHP على Coddy — الدرس 34 من 91.
في الدرس السابق، رأيت تعدد الأشكال (polymorphism) من خلال الوراثة، حيث تقوم الفئات الفرعية بتجاوز أساليب الفئات الأب. توفر الواجهات (Interfaces) طريقة قوية أخرى لتحقيق تعدد الأشكال، خاصة عند العمل مع فئات غير مرتبطة تشترك في سلوك شائع.
مع الواجهات، يأتي تعدد الأشكال من العقد نفسه. تضمن أي فئة تقوم بتنفيذ واجهة ما امتلاكها لدوال معينة، بغض النظر عن تسلسلها الهرمي للوراثة:
<?php
interface Notifiable {
public function send(string $message);
}
class EmailNotifier implements Notifiable {
public function send(string $message) {
return "Email: " . $message;
}
}
class SMSNotifier implements Notifiable {
public function send(string $message) {
return "SMS: " . $message;
}
}
class PushNotifier implements Notifiable {
public function send(string $message) {
return "Push: " . $message;
}
}
function notify(Notifiable $notifier, string $message) {
return $notifier->send($message);
}
echo notify(new EmailNotifier(), "Hello") . "\n";
echo notify(new SMSNotifier(), "Hello") . "\n";
echo notify(new PushNotifier(), "Hello");
المخرجات:
Email: Hello
SMS: Hello
Push: Helloتعمل الدالة notify() مع أي كائن Notifiable. لا تشترك هذه الأصناف في صنف أب - فهي غير مرتبطة تماماً باستثناء تنفيذها لنفس الواجهة. هذه هي الميزة الأساسية على تعدد الأشكال القائم على الوراثة: يمكنك تجميع الأصناف غير المرتبطة حسب قدراتها بدلاً من أصلها.
نقطة رئيسية: يتيح تعدد الأشكال القائم على الواجهات (Interface-based polymorphism) استخدام الأصناف (classes) غير المرتبطة بشكل تبادلي بناءً على السلوك المشترك، مما يجعل الكود الخاص بك أكثر مرونة وانفصالاً عن تطبيقات محددة.
التحدي
سهللنقم ببناء نظام تخزين يوضح تعدد الأشكال القائم على الواجهات (interface-based polymorphism). ستقوم بإنشاء خلفيات تخزين مختلفة — نظام ملفات وقاعدة بيانات — لا يوجد بينهما شيء مشترك سوى أنهما ينفذان نفس واجهة التخزين. ستعمل وظيفة واحدة مع أي نوع تخزين، مما يوضح كيف تمكن الواجهات تعدد الأشكال بين فئات غير مرتبطة تماماً.
ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:
Storable.php— قم بتعريف واجهةStorableمع توقيعين للدوال:save($key, $data)وretrieve($key). يضمن هذا العقد أن أي نظام تخزين يمكنه تخزين واسترجاع البيانات، بغض النظر عن كيفية تنفيذه داخلياً.FileStorage.php— أنشئ فئةFileStorageالتي تنفذStorable. قم بتضمين ملف الواجهة. يجب أن تحتوي الفئة على خاصية خاصة$directoryيتم تعيينها من خلال المنشئ (constructor). قم بتنفيذsave($key, $data)لتعيد"Saving '$data' to file $this->directory/$key.txt". قم بتنفيذretrieve($key)لتعيد"Reading from file $this->directory/$key.txt".DatabaseStorage.php— أنشئ فئةDatabaseStorageالتي تنفذ أيضاًStorable. قم بتضمين ملف الواجهة. يجب أن تحتوي الفئة على خاصية خاصة$tableNameيتم تعيينها من خلال المنشئ. قم بتنفيذsave($key, $data)لتعيد"Inserting '$data' into table $this->tableName with key $key". قم بتنفيذretrieve($key)لتعيد"Selecting from table $this->tableName where key = $key".main.php— قم بتضمين كلا ملفي التخزين. أنشئ دالة تسمىstoreDataتقبل معاملStorable، ومفتاحاً، وبيانات. يجب أن تعيد الدالة نتيجة استدعاءsave()مع المفتاح والبيانات. أنشئ دالة أخرى تسمىfetchDataتقبل معاملStorableومفتاحاً، وتعيد نتيجة استدعاءretrieve().
ستتلقى أربعة مدخلات: مسار دليل، واسم جدول، ومفتاح، وبعض البيانات لتخزينها. أنشئ كائن FileStorage مع الدليل وكائن DatabaseStorage مع اسم الجدول. استخدم دالة storeData() الخاصة بك لحفظ البيانات باستخدام تخزين الملفات أولاً، ثم تخزين قاعدة البيانات. اطبع كل نتيجة في سطر خاص بها. ثم استخدم fetchData() للاسترجاع من تخزين الملفات واطبع تلك النتيجة.
لاحظ كيف تعمل storeData() و fetchData() بشكل متطابق مع كلا نوعي التخزين — فهما يهتمان فقط بأن الكائن ينفذ Storable. يمتلك نظام الملفات وقاعدة البيانات تطبيقات داخلية مختلفة تماماً، ومع ذلك فهما قابلان للتبادل من خلال عقد الواجهة.
ورقة مرجعية
تمكن الواجهات (Interfaces) تعدد الأشكال (polymorphism) من خلال السماح باستخدام الفئات غير المرتبطة بشكل متبادل بناءً على السلوك المشترك بدلاً من تسلسل الوراثة.
تضمن أي فئة تقوم بتنفيذ واجهة معينة وجود طرق (methods) محددة، مما يؤدي إلى إنشاء عقد يتيح سلوك تعدد الأشكال:
<?php
interface Notifiable {
public function send(string $message);
}
class EmailNotifier implements Notifiable {
public function send(string $message) {
return "Email: " . $message;
}
}
class SMSNotifier implements Notifiable {
public function send(string $message) {
return "SMS: " . $message;
}
}
function notify(Notifiable $notifier, string $message) {
return $notifier->send($message);
}
echo notify(new EmailNotifier(), "Hello");
echo notify(new SMSNotifier(), "Hello");تقبل الدالة notify() أي كائن يقوم بتنفيذ Notifiable، بغض النظر عن تسلسل الفئات. وهذا يجعل الكود أكثر مرونة وفصلاً عن التنفيذات المحددة.
الميزة الرئيسية: يقوم تعدد الأشكال القائم على الواجهة بتجميع الفئات غير المرتبطة حسب قدراتها، وليس حسب أصلها، مما يسمح باستخدام تنفيذات مختلفة تماماً بشكل متبادل من خلال عقد مشترك.
جرّب بنفسك
<?php
require_once 'FileStorage.php';
require_once 'DatabaseStorage.php';
// قراءة المدخلات
$directory = trim(fgets(STDIN));
$tableName = trim(fgets(STDIN));
$key = trim(fgets(STDIN));
$data = trim(fgets(STDIN));
// TODO: إنشاء دالة storeData تقبل Storable، ومفتاح (key)، وبيانات (data)
// يجب أن تعيد نتيجة استدعاء save() مع المفتاح والبيانات
// TODO: إنشاء دالة fetchData تقبل Storable ومفتاح (key)
// يجب أن تعيد نتيجة استدعاء retrieve() مع المفتاح
// TODO: إنشاء مثيل (instance) من FileStorage مع الدليل (directory)
// TODO: إنشاء مثيل (instance) من DatabaseStorage مع اسم الجدول
// TODO: استخدام storeData() لحفظ البيانات باستخدام تخزين الملفات، ثم طباعة النتيجة
// TODO: استخدام storeData() لحفظ البيانات باستخدام تخزين قاعدة البيانات، ثم طباعة النتيجة
// TODO: استخدام fetchData() للاسترجاع من تخزين الملفات، ثم طباعة النتيجة
?>يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات OOP
الملفات الخارجيةمقدمة في OOPClasses مقابل Objectsالكلمة المفتاحية $thisالـ Methodsالـ Propertiesالمنشئ (__construct)الهادم (__destruct)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسيةالكلمة المفتاحية parent::إعادة تعريف الدوال (Method Overriding)الكلمة المفتاحية finalالأصناف المجردة (Abstract Classes)مراجعة - هيكلية الموظفين7التغليف (Encapsulation)
العام، المحمي، الخاص (Public, Protected, Private)نظرة متعمقة على معدلات الوصولدوال Getters و Settersإخفاء المعلوماتميزة Constructor Promotion (8.0)مراجعة - نظام سجلات الطلاب10مفاهيم البرمجة كائنية التوجه المتقدمة
التركيب مقابل الوراثةحقن التبعيةالأصناف المجهولةالتعدادات (PHP 8.1)الألياف (PHP 8.1)تعمق في استنساخ الكائناتالمولدات والمكررات13أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المستودع2مساحات الأسماء والتحميل التلقائي
مقدمة إلى مساحات الأسماءالكلمة المفتاحية useمعيار التحميل التلقائي PSR-4أداة التحميل التلقائي Composerملخص - مشروع منظم5الواجهات والعقود
مقدمة إلى الواجهاتتنفيذ الواجهاتتنفيذ واجهات متعددةالواجهة مقابل الـ Abstract Classالـ Type Hinting مع الواجهاتملخص - حاسبة الأشكال8الميثودات السحرية (Magic Methods)
مقدمة في الميثودات السحرية__toString و __debugInfo__get, __set, __isset, __unset__call و __callStatic__clone واستنساخ الكائنات__serialize و __unserializeمراجعة - Custom Collection3خصائص الكلاس
خصائص الـ Instance مقابل الخصائص الساكنة (Static)الثوابت في الكلاساتالدوال والخصائص الساكنة (Static)الخصائص الخاصة (Private) والمحمية (Protected)خصائص للقراءة فقط (Readonly) في PHP 8.1مراجعة - مدير الحساب البنكي6تعدد الأشكال (Polymorphism)
مراجعة إعادة تعريف الدوال (Method Overriding)تعدد الأشكال عبر الـ Interfacesتلميح النوع (Type Hinting) وأنواع الاتحاد (Union Types)الربط الاستاتيكي المتأخر (Late Static Binding)ملخص - معالج الدفع (Payment Processor)