نمط أسلوب القالب Temlate Method
نمط أسلوب القالب Template Method هو نمط تصميم سلوكي يحدد هيكل الخوارزمية في الفئة الرئيسية (superclass) لكنه في نفس الوقت يسمح للفئات الفرعية بتغيير خطوات بعينها من تلك الخوارزمية دون تغيير هيكلها.
المشكلة
تخيل أنك تنشئ برنامج للتنقيب عن البيانات يحلل مستندات الشركات، ويغذي المستخدمون البرنامج بمستندات بصيغ متعددة (PDF-DOC-CSV)، ويحاول البرنامج استخراج بيانات ذات قيمة منها ويضعها في صيغة موحدة. سيعمل أول إصدار من البرنامج مع مستندات DOC فقط، والإصدار التالي سيكون قادرًا على دعم ملفات CSV، ثم في الشهر الذي يليه ستكون قد "علَّمتَه" أن يستخرج البيانات من ملفات PDF.
الصورة.
تحتوي فئات التنقيب عن البيانات على شيفرات مكررة كثيرة.
ستلاحظ في مرحلة ما أن الفئات الثلاث بها شيفرات مكررة، ورغم أن شيفرة التعامل مع صيغ البيانات المختلفة كانت مختلفة تمامًا في جميع الفئات، إلا أن شيفرة معالجة وتحليل البيانات تكاد تكون متطابقة. هناك مشكلة أخرى تتعلق بشيفرة العميل التي استخدمت تلك الفئات، فقد كان بها الكثير من العبارات الشرطية التي اتخذت التسلسل الصحيح للإجراءات وفقًا لفئة الكائن المعالِج.
وإذا كانت فئات المعالجة الثلاث تحتوي على واجهة مشتركة أو فئة أساسية فستكون قادرًا على إزالة العبارات الشرطية في شيفرة العميل واستخدام تعدد الأشكال (polymorphism) عند استدعاء أساليب على كائن المعالجة.
الحل
يقترح نمط أسلوب القالب أن تقسم الخوارزمية إلى سلسلة من الخطوات ثم تحول تلك الخطوات إلى أساليب وتضع سلسلة من الاستدعاءات لتلك الأساليب داخل أسلوب قالب وحيد. وقد تكون تلك الخطوات مجردة abstract
أو لها بعض التطبيقات.
ومن أجل استخدام تلك الخوارزمية ينبغي على العميل أن يوفر فئته الفرعية ويطبق كل الخطوات المجردة ويغير بعض الخطوات الاختيارية إن دعت الحاجة (لكن ليس أسلوب القالب نفسه).
لنرى الآن كيف ستكون نتيجة ذلك في برنامج التنقيب عن البيانات، يمكننا إنشاء فئة أساسية لخوارزميات التحليل الثلاثة التي لدينا، وستحدد تلك الفئة أسلوب قالب يتكون من سلسلة من الاستدعاءات إلى عدة خطوات لمعالجة المستندات.
الصورة.
(ش.2) يقسم أسلوب القالب الخوارزمية إلى خطوات سامحًا للفئات الفرعية بتغيير تلك الخطوات لكنها لا تغير الأسلوب الفعلي.
في البداية، يمكننا التصريح عن كل الخطوات على أنها تجريدية abstract
مجبرًا الفئات الفرعية على توفير التطبيقات الخاصة بها لتلك الأساليب، وفي حالتنا هذه فإن الفئات الفرعية لديها كل التطبيقات اللازمة، لذا لا يتبقى لدينا شيء سوى تعديل توقيعات (signature) تلك الأساليب لتطابق أساليب الفئة الرئيسية (superclass).
والآن لنرى ما يمكننا فعله لنتخلص من الشيفرة المكررة، من الواضح أن شيفرة فتح وإغلاق الملفات واستخراج وتحليل البيانات مختلفة لكل صيغة، لذا لا فائدة ترجى من تعديل تلك الأساليب. لكن من ناحية أخرى فإن تطبيق بعض الخطوات الأخرى يتشابه كثيرًا، مثل تحليل البيانات الخام وإنشاء التقارير، لذا يمكننا سحبها إلى الفئة الرئيسية حيث تستطيع الفئات الفرعية أن تتشارك تلك الشيفرة.
لدينا هنا نوعان من الخطوات:
- خطوات تجريدية يجب على كل فئة فرعية تطبيقها.
- خطوات اختيارية بها بعض التطبيق الافتراضي (default implementation)، لكن يمكن تخطيها عند الحاجة.
هناك نوع آخر من الخطوات اسمه الخطاطيف (hooks)، وهي خطوات اختيارية، متنها (body) فارغ. وسيعمل أسلوب القالب حتى لو لم يتم تخطي الخطاف، وتوضع الخطاطيف عادة قبل أو بعد الخطوات الحرجة في الخوارزميات لتزود الفئات الفرعية بنقاط توسعة إضافية للخوارزمية.
مثال واقعي
الصورة.
(ش.3) يمكن تعديل خطة هندسية نموذجية لتناسب احتياجات العميل.
يمكن استخدام أسلوب القالب في الإنشاءات الكبيرة للمنازل، وقد تحتوي الخطة الهندسية الافتراضية على نقاط توسعة عديدة تسمح للمالك المحتمل بتعديل بعض التفاصيل للمنزل الذي يريده، إذ بإمكانه تغيير كل خطوة في البناء قليلًا لتجعل المنزل الناتج مختلفًا بشكل ما عن غيره.
البنية
الصورة.
- تصرح الفئة المجردة (Abstract Class) عن أساليب تتصرف كخطوات للخوارزمية، إضافة إلى أسلوب القالب الفعلي الذي يستدعي تلك الأساليب بترتيب محدد، ويصرَّح عن تلك الأساليب على أنها مجردة
abstract
أو لديها بعض التطبيق الافتراضي (default implementation). - تستطيع الفئات الحقيقية (Concrete Classes) أن تتخطى كل الخطوات، باستثناء أسلوب القالب نفسه.
مثال توضيحي
يوفر نمط أسلوب القالب في هذا المثال هيكلًا للفروع المتعددة للذكاء الصناعي في لعبة فيديو من النوع التخطيطي (strategy).
الصورة.
(ش.5) فئات AI للعبة فيديو بسيطة.
تحتوي جميع السباقات في اللعبة على نفس الوحدات والمباني تقريبًا، لذا يمكنك إعادة استخدام نفس بنية الذكاء الصناعي لسباقات مختلفة مع القدرة على تخطي (override) بعض التفاصيل. وبهذا المنظور تستطيع تخطي الذكاء الصناعي للـ orcs لجعلها أكثر عدوانية، وتجعل البشر دفاعيين أكثر، وتجعل الوحوش غير قادرة على بناء أي شيء.
إذا أردت إضافة سباق جديد إلى اللعبة فستحتاج إلى إنشاء فئة ذكاء صنعي فرعية جديدة، وتتخطى الأساليب الافتراضية المصرّح عنها في فئة الذكاء الصنعي الأساسية.
// تحدد الفئة المجردة أسلوب قالب يحتوي على هيكل لخوارزمية
// مكونة من استدعاءات تُستخدم عادة لتجريد عمليات
// وتستخدم الفئات الفرعية الحقيقية ، primitive operations أولية
// تلك العمليات لكن دون المساس بأسلوب القالب نفسه.
class GameAI is
// يحدد أسلوب القالب هيكل الخوارزمية.
method turn() is
collectResources()
buildStructures()
buildUnits()
attack()
// قد تُستخدم بعض الخطوات في الفئة الأساسية.
method collectResources() is
foreach (s in this.builtStructures) do
s.collect()
// وقد يُحدد بعضها على أنها خطوات مجردة.
abstract method buildStructures()
abstract method buildUnits()
// يمكن للفئة أن يكون لها أكثر من أسلوب قالب واحد.
method attack() is
enemy = closestEnemy()
if (enemy == null)
sendScouts(map.center)
else
sendWarriors(enemy.position)
abstract method sendScouts(position)
abstract method sendWarriors(position)
// يجب أن تطبق الفئات الحقيقية كل العمليات المجردة للفئة الأساسية
// أسلوب القالب نفسه (override) لكن دون تخطي.
class OrcsAI extends GameAI is
method buildStructures() is
if (there are some resources) then
// أنشئ المزارع، ثم الثكنات، ثم المعاقل.
method buildUnits() is
if (there are plenty of resources) then
if (there are no scouts)
// أنشئ ساعيًا وأضفه إلى الكشافة.
else
// أنشئ جنديًا وأضفه إلى المحاربين.
// ...
method sendScouts(position) is
if (scouts.length > 0) then
// أرسل الكشافة إلى الموقع الهدف.
method sendWarriors(position) is
if (warriors.length > 5) then
// أرسل المحاربين إلى الموقع الهدف.
// تستطيع الفئات الفرعية أن تتخطى بعض العمليات عبر التطبيق
// الافتراضي.
class MonstersAI extends GameAI is
method collectResources() is
// resources الوحوش لا تجمع الموارد.
method buildStructures() is
// الوحوش لا تبني مباني.
method buildUnits() is
// الوحوش لا تبني وحدات.
قابلية التطبيق
- استخدم أسلوب القالب عندما تريد السماح للعملاء بتوسيع خطوات معينة من الخوارزمية دون السماح بتوسيع الخوارزمية كلها أو بنيتها.
يسمح لك نمط أسلوب القالب بتحويل الخوارزمية الأحادية إلى سلسلة من الخطوات المستقلة التي يمكن توسعتها بسهولة بالفئات الفرعية مع الحفاظ على بنية الخوارزمية المحددة في الفئة الرئيسية (superclass) كما هي.
- استخدم النمط حين يكون لديك عدة فئات تحتوي على نفس الخوارزميات تقريبًا مع بعض الاختلافات الصغيرة. كنتيجة لذلك فقد تحتاج هنا إلى تعديل جميع الفئات حين تتغير الخوارزمية.
حين تحول الخوارزمية إلى أسلوب قالب فيمكنك سحب بعض الخطوات التي تتشابه في التطبيقات إلى فئة رئيسية، مما يلغي وجود الشيفرات المكررة. أما الشيفرة التي تتغير بين الفئات الفرعية فستبقى كما هي.
كيفية الاستخدام
- حلل الخوارزمية الهدف لمعرفة إن كنت تستطيع تقسيمها إلى خطوات أم لا، وحدد أي الخطوات المشتركة بين كل الفئات الفرعية وأيها ستكون خطوات فريدة.
- أنشئ فئة مجردة أساسية وصرح عن أسلوب القالب ومجموعة من الأساليب المجردة التي تمثل خطوات الخوارزمية. ثم ضع الهيكل العام للخوارزمية في أسلوب القالب عبر تنفيذ الخطوات المناسبة. اجعل أسلوب القالب
final
لمنع الفئات الفرعية من التعديل عليه. - لا بأس أن تكون كل الخطوات مجردة، لكن بعض الخطوات قد تستفيد من وجود تطبيق افتراضي (default implementation) فيها. كذلك، ليس شرطًا أ، تطبق الفئات الفرعية تلك الأساليب.
- أضف خطاطيف بين الخطوات الحرجة للخوارزمية.
- أنشئ فئة فرعية حقيقية لكل شكل من أشكال الخوارزمية، يجب أن تطبق تلك الفئة جميع الخطوات المجردة، كما قد تتخطى بعض الخطوات الاختيارية أيضًا.
المزايا والعيوب
المزايا
- تستطيع السماح للعملاء بتخطي أجزاء محددة من خوارزمية كبيرة، لتقلل تأثرهم بالتغيرات التي تحدث لأجزاء أخرى من الخوارزمية.
- تستطيع سحب الشيفرة المكررة إلى فئة رئيسية.
العيوب
- قد يكون بعض العملاء محدودًا بهيكل الخوارزمية الذي توفره له.
- قد تخرق مبدأ استبدال ليزكوف (Liskov Substitution Principle) عبر منع التطبيق الافتراضي لخطوة ما من خلال فئة فرعية.
- تميل أساليب القالب إلى أن تكون صعبة الصيانة والتعديل كلما زاد عدد الخطوات فيها.
العلاقات مع الأنماط الأخرى
- نمط أسلوب المصنع هو صورة مخصصة من أسلوب القالب، وقد يكون أسلوب المصنع نفس الوقت خطوةً داخل أسلوب قالب كبير.
- يُبنى أسلوب القالب على الاكتساب (inheritance)، إذ يسمح لك بتغيير أجزاء من خوارزمية من خلال توسيع تلك الأجزاء في فئات فرعية. أما نمط الخطة (ٍStrategy) فيبنى على التركيب، بحيث تستطيع تغيير أجزاء من سلوك الكائن من خلال تزويده باستراتيجيات مختلفة تتوافق مع ذلك السلوك. ويعمل أسلوب القالب على مستوى الفئة لذا يعد نمطًا ساكنًا (static)، أما نمط الخطة فيعمل على مستوى الكائن، مما يسمح له بتبديل السلوك أثناء وقت التشغيل.
الاستخدام في لغة جافا
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام:
الاستخدام في لغة #C
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام:
مثال تصوري
يوضح هذا المثال بنية نمط أسلوب القالب، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
الاستخدام في لغة PHP
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام:
مثال تصوري
يوضح هذا المثال بنية نمط أسلوب القالب، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
سيكون من السهل عليك استيعاب المثال التالي بعد تعلم بنية النمط، بناء على استخدام واقعي له في لغة PHP.
الاستخدام في لغة بايثون
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام:
مثال تصوري
يوضح هذا المثال بنية نمط أسلوب القالب، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
الاستخدام في لغة روبي
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام:
مثال تصوري
يوضح هذا المثال بنية نمط أسلوب القالب، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
الاستخدام في لغة Swift
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام:
مثال تصوري
يوضح هذا المثال بنية نمط أسلوب القالب، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
بعد تعلم بنية النمط سيكون من السهل عليك استيعاب المثال التالي المبني على حالة واقعية في لغة Swift.
الاستخدام في لغة TypeScript
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام:
مثال تصوري
يوضح هذا المثال بنية نمط أسلوب القالب، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟