أنماط التصميم مخصصة فقط لمهندسي Java - على الأقل هذا ما كنت تعتقده دائمًا. في الواقع، أنماط التصميم مفيدة للجميع. إذا لم تكن هذه الأدوات حكراً على «رواد الفضاء المعماريين»، فما هي إذن؟ لماذا هي مفيدة في تطبيقات PHP؟ تشرح هذه المقالة هذه المشكلات.
قدم كتاب أنماط التصميم أنماط التصميم لمجتمع البرمجيات. مؤلفو الكتاب هم إريك جاما، وريتشارد هيلم، ورالف جونسون، وجون فليسيدس ديزاين (المعروفين باسم "عصابة الأربعة"). المفاهيم الأساسية وراء أنماط التصميم المقدمة بسيطة للغاية. بعد سنوات من ممارسة تطوير البرمجيات، اكتشف جاما وآخرون أنماطًا معينة ذات تصميمات ثابتة، تشبه إلى حد كبير تصميم المهندسين المعماريين للمنازل والمباني، وتطوير نماذج للمكان الذي يجب أن يكون فيه الحمام أو كيفية بناء المطبخ. إن استخدام هذه القوالب، أو أنماط التصميم، يعني تصميم مباني أفضل بشكل أسرع. وينطبق نفس المفهوم على البرمجيات.
لا تمثل أنماط التصميم طريقة مفيدة لتطوير برامج قوية بشكل أسرع فحسب، ولكنها توفر أيضًا طريقة لتغليف الأفكار الكبيرة بعبارات ودية. على سبيل المثال، يمكنك القول أنك تكتب نظام مراسلة يوفر اقترانًا فضفاضًا، أو يمكنك القول أنك تكتب نمطًا يسمى Observer.
إن إظهار قيمة الأنماط بأمثلة أصغر أمر صعب للغاية. غالبًا ما يبدو هذا أمرًا مبالغًا فيه، نظرًا لأن الأنماط تعمل فعليًا في قواعد أكواد برمجية كبيرة. لا توضح هذه المقالة تطبيقًا كبيرًا، لذلك تحتاج إلى التفكير في طرق لتطبيق مبادئ المثال في تطبيقك الكبير - وليس الكود نفسه الموضح في هذه المقالة. هذا لا يعني أنه لا ينبغي عليك استخدام الأنماط في التطبيقات الصغيرة. تبدأ العديد من التطبيقات الجيدة كتطبيقات صغيرة وتتطور إلى تطبيقات كبيرة، لذلك لا يوجد سبب لعدم البناء على هذه الأنواع من ممارسات البرمجة القوية. الآن بعد أن فهمت أنماط التصميم وسبب فائدتها، دعنا نلقي نظرة على الأنماط الخمسة الشائعة الاستخدام في PHP V5.
نمط المصنع
في الأصل في كتاب أنماط التصميم، تشجع العديد من أنماط التصميم على استخدام أدوات التوصيل السائبة. لفهم هذا المفهوم، من الأفضل الحديث عن الرحلة الشاقة التي يمر بها العديد من المطورين في العمل على الأنظمة الكبيرة. عندما تقوم بتغيير جزء واحد من التعليمات البرمجية، يمكن أن تحدث مشكلات، ويمكن أن تحدث فواصل متتالية في أجزاء أخرى من النظام - الأجزاء التي كنت تعتقد أنها غير مرتبطة تمامًا.
المشكلة هي اقتران ضيق. تعتمد الوظائف والفئات في جزء واحد من النظام بشكل كبير على سلوك وبنية الوظائف والفئات في الأجزاء الأخرى من النظام. تريد مجموعة من الأنماط التي تسمح لهذه الفئات بالتواصل مع بعضها البعض، لكنك لا تريد ربطها ببعضها البعض بإحكام لتجنب التشابك. في الأنظمة الكبيرة، يعتمد الكثير من التعليمات البرمجية على عدد قليل من الفئات الرئيسية. قد تنشأ صعوبات عندما تحتاج هذه الفئات إلى التغيير. على سبيل المثال، لنفترض أن لديك فئة مستخدم تقرأ من ملف. تريد تغييره إلى فئة مختلفة تُقرأ من قاعدة البيانات، ومع ذلك، تشير جميع التعليمات البرمجية الخاصة بك إلى الفئة الأصلية التي تُقرأ من الملف. في هذا الوقت، سيكون من المناسب جدًا استخدام وضع المصنع.
نمط المصنع هو فئة تحتوي على طرق معينة لإنشاء كائنات لك. يمكنك استخدام فئات المصنع لإنشاء كائنات دون استخدام الجديد مباشرةً. بهذه الطريقة، إذا كنت تريد تغيير نوع الكائن الذي تم إنشاؤه، فستحتاج فقط إلى تغيير المصنع. يتم تغيير جميع التعليمات البرمجية التي تستخدم هذا المصنع تلقائيًا.
تعرض القائمة 1 مثالاً لفئة المصنع. يتكون جانب الخادم من المعادلة من جزأين: قاعدة بيانات ومجموعة من صفحات PHP التي تسمح لك بإضافة تعليقات وطلب قائمة بالملاحظات والحصول على مقالات تتعلق بتعليقات معينة.
القائمة 1. Factory1.php
<?php واجهة مستخدم { الدالة getName(); }
يقوم مستخدم الطبقة بتنفيذ IUser { الوظيفة العامة __construct( $id ) { }
الوظيفة العامة getName () { إرجاع "جاك" ؛ } }
فئة UserFactory { إنشاء وظيفة ثابتة عامة (معرف $) { إرجاع مستخدم جديد($id); } }
$uo = UserFactory::Create( 1 ); echo( $uo->getName()."n" ); ?> |
تحدد واجهة IUser الإجراءات التي يجب أن يقوم بها كائن المستخدم. يُطلق على تطبيق IUser اسم المستخدم، وتقوم فئة المصنع UserFactory بإنشاء كائنات IUser. يمكن تمثيل هذه العلاقة بواسطة UML في الشكل 1.
الشكل 1. فئة المصنع وواجهة IUser ذات الصلة وفئة المستخدم |
إذا قمت بتشغيل هذا الكود في سطر الأوامر باستخدام مترجم PHP، فسوف تحصل على النتائج التالية:
سيطلب رمز الاختبار كائن المستخدم من المصنع ويخرج نتيجة طريقة getName.
هناك نوع مختلف من نمط المصنع الذي يستخدم أساليب المصنع. تقوم هذه الأساليب الثابتة العامة في الفصل ببناء كائنات من هذا النوع. هذه الطريقة مفيدة إذا كان من المهم إنشاء كائنات من هذا النوع. على سبيل المثال، لنفترض أنك بحاجة إلى إنشاء كائن ثم تعيين عدد من الخصائص. يقوم هذا الإصدار من نمط المصنع بتغليف العملية في مكان واحد، لذلك لا يتعين عليك نسخ رمز التهيئة المعقد ولصقه في جميع أنحاء قاعدة التعليمات البرمجية. تعرض القائمة 2 مثالاً لاستخدام طريقة المصنع.
القائمة 2. Factory2.php
<?php واجهة مستخدم { الدالة getName(); }
يقوم مستخدم الطبقة بتنفيذ IUser { تحميل وظيفة ثابتة عامة (معرف $) { إرجاع مستخدم جديد($id); }
إنشاء وظيفة ثابتة عامة () { إرجاع مستخدم جديد (فارغ)؛ }
الوظيفة العامة __construct( $id ) { }
الوظيفة العامة getName () { إرجاع "جاك" ؛ } }
$uo = المستخدم::Load( 1 ); echo( $uo->getName()."n" ); ?> |
هذا الرمز هو أبسط من ذلك بكثير. يحتوي على واجهة IUser واحدة فقط وفئة مستخدم تقوم بتنفيذ هذه الواجهة. تحتوي فئة المستخدم على طريقتين ثابتتين لإنشاء الكائنات. يمكن تمثيل هذه العلاقة بواسطة UML في الشكل 2.
الشكل 2. واجهة المستخدم وفئة المستخدم مع طريقة المصنع |
يؤدي تشغيل البرنامج النصي في سطر الأوامر إلى الحصول على نفس نتائج القائمة 1، كما يلي:
كما ذكرنا أعلاه، قد تبدو مثل هذه الأوضاع في بعض الأحيان مبالغة في البيئات الأصغر. ومع ذلك، فمن الأفضل أن تتعلم هذا النوع القوي من البرمجة الذي يمكنك تطبيقه على المشاريع من أي حجم.
وضع عنصر واحد
بعض موارد التطبيق حصرية نظرًا لوجود مورد واحد فقط من هذا النوع. على سبيل المثال، تكون الاتصالات بقاعدة البيانات من خلال مؤشر قاعدة البيانات حصرية. تريد مشاركة مقبض قاعدة البيانات عبر التطبيق الخاص بك لأنه يمثل عبئًا عند إبقاء الاتصال مفتوحًا أو مغلقًا، بل وأكثر من ذلك أثناء عملية جلب صفحة واحدة.
وضع العنصر الواحد يلبي هذا المطلب. إذا كان التطبيق يحتوي على كائن واحد فقط في المرة الواحدة، فسيكون هذا الكائن مفردًا. يُظهر الكود الموجود في القائمة 3 عنصرًا واحدًا لاتصال قاعدة البيانات في PHP V5.
القائمة 3. Singleton.php
<?php require_once("DB.php");
اتصال قاعدة البيانات فئة { الحصول على وظيفة ثابتة عامة () { ثابت $db = null; إذا ($db == فارغ) $db = new DatabaseConnection(); إرجاع $ ديسيبل؛ }
خاص $_handle = null; وظيفة خاصة __بناء () { $dsn = 'mysql://root:password@localhost/photos'; $this->_handle =& DB::Connect( $dsn, array() ); }
مقبض الوظيفة العامة () { إرجاع $this->_handle; } }
print( "Handle = ".DatabaseConnection::get()->handle()."n" ); print( "Handle = ".DatabaseConnection::get()->handle()."n" ); ?> |
يعرض هذا الرمز فئة واحدة تسمى DatabaseConnection. لا يمكنك إنشاء DatabaseConnection الخاص بك لأن المنشئ خاص. ولكن باستخدام طريقة get الثابتة، يمكنك الحصول على كائن DatabaseConnection واحد فقط والحصول عليه. يظهر UML لهذا الرمز في الشكل 3.
الشكل 3. عنصر واحد اتصال قاعدة البيانات |
أفضل دليل على ذلك هو أن مؤشر قاعدة البيانات الذي تم إرجاعه بواسطة أسلوب المقبض هو نفسه بين المكالمتين. يمكنك تشغيل التعليمات البرمجية في سطر الأوامر لمراقبة ذلك.
% PHP Singleton.php المقبض = معرف الكائن رقم 3 المقبض = معرف الكائن رقم 3 % |
المقبضان اللذان تم إرجاعهما هما نفس الكائن. إذا كنت تستخدم عنصرًا واحدًا لاتصال قاعدة البيانات في التطبيق الخاص بك، فيمكنك إعادة استخدام نفس المقبض في كل مكان.
يمكنك استخدام المتغيرات العامة لتخزين مقابض قاعدة البيانات، ومع ذلك، فإن هذا الأسلوب مناسب فقط للتطبيقات الأصغر حجمًا. في التطبيقات الأكبر حجمًا، تجنب استخدام المتغيرات العامة واستخدم الكائنات والأساليب للوصول إلى الموارد. نمط المراقب
يمنحك نمط المراقب طريقة أخرى لتجنب الاقتران الضيق بين المكونات. النمط بسيط للغاية: الكائن يجعل نفسه قابلاً للملاحظة عن طريق إضافة طريقة تسمح لكائن آخر، المراقب، بتسجيل نفسه. عندما يتغير كائن يمكن ملاحظته، فإنه يرسل رسائل إلى المراقبين المسجلين. يستخدم هؤلاء المراقبون هذه المعلومات لإجراء عمليات مستقلة عن الكائن الذي يمكن ملاحظته. والنتيجة هي أن الكائنات يمكنها التحدث مع بعضها البعض دون الحاجة إلى فهم السبب. مثال بسيط هو قائمة المستخدمين في النظام. يعرض الكود الموجود في القائمة 4 قائمة بالمستخدمين، وعندما تتم إضافة مستخدم، فإنه يرسل رسالة. يمكن ملاحظة هذه القائمة من خلال مراقب السجل الذي يرسل رسائل عند إضافة مستخدم.
القائمة 4. Observer.php
<?php واجهة IObserver { وظيفة onChanged( $sender, $args ); }
واجهة يمكن ملاحظتها { وظيفة addObserver($observer); }
تطبق فئة UserList IObservable { خاص $_observers = array();
الوظيفة العامة addCustomer( $name ) { foreach($this->_observers كـ $obs) $obs->onChanged( $this, $name ); }
الوظيفة العامة addObserver($observer) { $this->_observers []= $observer; } }
فئة UserListLogger تنفذ IObserver { الوظيفة العامة onChanged( $sender, $args ) { echo( "تمت إضافة '$args' إلى قائمة المستخدمينn" ); } }
$ul = new UserList(); $ul->addObserver( new UserListLogger() ); $ul->addCustomer( "جاك" ); ?> |
يحدد هذا الكود أربعة عناصر: واجهتين وفئتين. تحدد واجهة IObservable الكائنات التي يمكن ملاحظتها، ويقوم UserList بتنفيذ هذه الواجهة لتسجيل نفسه على أنه يمكن ملاحظته. تحدد قائمة IObserver كيفية أن تصبح مراقبًا. يقوم UserListLogger بتنفيذ واجهة IObserver. وتظهر هذه العناصر في UML في الشكل 4.
الشكل 4. قائمة المستخدمين القابلة للملاحظة ومسجل أحداث قائمة المستخدمين |
إذا قمت بتشغيله من سطر الأوامر، فسترى الإخراج التالي:
% PHP Observer.php تمت إضافة "جاك" إلى قائمة المستخدمين % |
يقوم رمز الاختبار بإنشاء قائمة مستخدم ويضيف مراقب UserListLogger إليها. ثم أضف مستهلكًا وأخطر UserListLogger بهذا التغيير.
من المهم أن ندرك أن UserList لا يعرف ما الذي سيفعله المُسجل. قد يكون هناك مستمع واحد أو أكثر يقومون بعمليات أخرى. على سبيل المثال، قد يكون لديك مراقب يرسل رسالة إلى المستخدمين الجدد، يرحب بهم في النظام. تكمن قيمة هذا الأسلوب في أن UserList يتجاهل كافة الكائنات التي تعتمد عليه ويركز بشكل أساسي على الحفاظ على قائمة المستخدمين وإرسال الرسائل عندما تتغير القائمة.
لا يقتصر هذا النمط على الكائنات الموجودة في الذاكرة. إنه الأساس لأنظمة الاستعلام عن الرسائل المستندة إلى قاعدة البيانات والمستخدمة في التطبيقات الأكبر حجمًا. سلسلة من وضع الأوامر
يعتمد نمط سلسلة الأوامر على موضوعات مترابطة بشكل غير محكم، أو إرسال رسائل، أو أوامر، أو طلبات، أو أي شيء آخر من خلال مجموعة من المعالجات. يصدر كل معالج حكمه الخاص حول ما إذا كان يمكنه التعامل مع الطلب. إذا أمكن، تتم معالجة الطلب وتتوقف العملية. يمكنك إضافة معالجات أو إزالتها من النظام دون التأثير على المعالجات الأخرى. تظهر القائمة 5 مثالاً على هذا النمط.
القائمة 5. Chain.php
<?php InterfaceICommand { وظيفة onCommand( $name, $args ); }
فئة CommandChain { خاص $_commands = array();
الوظيفة العامة addCommand($cmd) { $this->_commands []= $cmd; }
الوظيفة العامة runCommand( $name, $args ) { foreach($this->_commands كـ $cmd) { إذا ( $cmd->onCommand( $name, $args ) ) يعود؛ } } }
فئة UserCommand تنفذ ICommand { وظيفة عامة onCommand( $name, $args ) { إذا قام ($name != 'addUser') بإرجاع خطأ؛ echo( "معالجة أوامر المستخدم 'addUser'n"); عودة صحيحة؛ } }
تقوم فئة MailCommand بتنفيذ ICommand { وظيفة عامة onCommand( $name, $args ) { إذا قام ($name != 'mail') بإرجاع خطأ؛ echo( "معالجة MailCommand 'mail'n"); عودة صحيحة؛ } }
$cc = new CommandChain(); $cc->addCommand(new UserCommand()); $cc->addCommand(new MailCommand()); $cc->runCommand( 'addUser', null ); $cc->runCommand( 'mail', null ); ?> |
يحدد هذا الرمز فئة CommandChain التي تحتفظ بقائمة كائنات ICommand. يمكن لكلا الفئتين تنفيذ واجهة ICommand - واحدة تستجيب لطلبات البريد، والأخرى تستجيب لإضافة مستخدمين. ويبين الشكل 5 لغة النمذجة الموحدة (UML).
الشكل 5. سلسلة القيادة والأوامر المرتبطة بها |
إذا قمت بتشغيل برنامج نصي يحتوي على بعض التعليمات البرمجية للاختبار، فستحصل على الإخراج التالي:
% PHP chain.php معالجة أمر المستخدم "addUser" MailCommand يتعامل مع "البريد" % |
يقوم الكود أولاً بإنشاء كائن CommandChain وإضافة مثيلين لكائن الأمر إليه. ثم قم بتشغيل الأمرين لمعرفة من استجاب للأوامر. إذا كان اسم الأمر يتطابق مع UserCommand أو MailCommand، فسيفشل الرمز ولا يحدث أي إجراء. يعد نمط سلسلة الأوامر ذا قيمة عند إنشاء بنية قابلة للتطوير للتعامل مع الطلبات، ويمكن حل العديد من المشكلات باستخدامه. نمط الاستراتيجية
نمط التصميم الأخير الذي نغطيه هو نمط الإستراتيجية. في هذا النمط، يتم استخراج الخوارزميات من فئات معقدة وبالتالي يمكن استبدالها بسهولة. على سبيل المثال، إذا كنت تريد تغيير طريقة ترتيب الصفحات في محركات البحث، فإن وضع الإستراتيجية يعد خيارًا جيدًا. فكر في أجزاء محرك البحث - محرك يجتاز الصفحات، ومحرك يرتب كل صفحة، وآخر يفرز النتائج بناءً على التصنيف. في الأمثلة المعقدة، تكون هذه الأجزاء كلها في نفس الفئة. باستخدام نمط الإستراتيجية، يمكنك وضع جزء الترتيب في فئة أخرى لتغيير طريقة ترتيب الصفحة دون التأثير على بقية كود محرك البحث.
وكمثال أبسط، تعرض القائمة 6 فئة قائمة المستخدمين التي توفر طريقة للعثور على مجموعة من المستخدمين بناءً على مجموعة من سياسات التوصيل والتشغيل.
القائمة 6. Strategy.php
<?php واجهة استراتيجية { مرشح الوظيفة (سجل $) ؛ }
تقوم فئة FindAfterStrategy بتنفيذ IStrategy { خاص $_name؛
الوظيفة العامة __construct( $name ) { $this->_name = $name; }
مرشح الوظيفة العامة (سجل $) { إرجاع strcmp( $this->_name, $record ) <= 0; } }
تقوم فئة RandomStrategy بتنفيذ IStrategy { مرشح الوظيفة العامة (سجل $) { راند العودة(0, 1) >= 0.5; } }
قائمة المستخدمين فئة { خاص $_list = array();
الوظيفة العامة __construct( $names ) { إذا (أسماء $!= فارغة) { foreach (أسماء $ كـ $name) { $this->_list []= $name; } } }
إضافة وظيفة عامة (اسم $) { $this->_list []= $name; }
البحث عن الوظيفة العامة (فلتر $) { $recs = array(); foreach($this->_list كمستخدم $) { إذا ( $filter->filter( $user ) ) $recs []= $user; } إرجاع $recs؛ } }
$ul = new UserList( array( "Andy", "Jack", "Lori", "Megan" ) ); $f1 = $ul->find( new FindAfterStrategy( "J" ) ); print_r($f1);
$f2 = $ul->find( new RandomStrategy() ); print_r($f2); ?> |
الشكل 6. قائمة المستخدمين والسياسة المستخدمة لاختيار المستخدمين |
فئة UserList عبارة عن غلاف حول مجموعة من الأسماء. يقوم بتنفيذ طريقة البحث، والتي تستخدم واحدة من عدة إستراتيجيات لتحديد مجموعة فرعية من هذه الأسماء. يتم تعريف هذه الاستراتيجيات من خلال واجهة IStrategy، التي لها تطبيقان: أحدهما يختار المستخدم عشوائيًا، والآخر يختار جميع الأسماء بعد الاسم المحدد. عند تشغيل رمز الاختبار، تحصل على الإخراج التالي:
%php استراتيجية.php صفيف ( [0] => جاك [1] =>لوري [2] =>ميجان ) صفيف ( [0] => أندي [1] =>ميجان ) % |
يقوم رمز الاختبار بتشغيل نفس قائمة المستخدمين لكلا الاستراتيجيتين ويعرض النتائج. في الحالة الأولى، تبحث الإستراتيجية عن أي اسم يتبع J، لذا ستحصل على جاك ولوري وميغان. تقوم الإستراتيجية الثانية باختيار الأسماء بشكل عشوائي، مما يؤدي إلى نتائج مختلفة في كل مرة. في هذه الحالة، النتائج هي آندي وميغان.
يعد نمط الإستراتيجية مثاليًا لأنظمة إدارة البيانات المعقدة أو أنظمة معالجة البيانات التي تتطلب درجة عالية من المرونة في كيفية تصفية البيانات أو البحث فيها أو معالجتها.
خاتمة
تقدم هذه المقالة عددًا قليلاً من أنماط التصميم الأكثر شيوعًا المستخدمة في تطبيقات PHP. يتم عرض المزيد من أنماط التصميم في كتاب أنماط التصميم. لا تدع سحر الهندسة المعمارية يوقفك. الأنماط هي فكرة رائعة تعمل بأي لغة برمجة وعلى أي مستوى مهارة.