نمط الخطة Strategy

من موسوعة حسوب
مراجعة 09:52، 29 سبتمبر 2019 بواسطة أسامه-دمراني (نقاش | مساهمات) (2.2 محتوى | 3.0 الصور | 4.0 انظر أيضًا ومصادر.)

نمط الخطة هو نمط تصميم سلوكي يسمح لك بتحديد عائلة من الخوارزميات ووضع كل واحدة منها داخل فئة منفصلة، ومن ثم جعل كائناتها تقبل التبادل (interchangeable).

المشكلة

(ش.1) شيفرة المستكشف Navigator صارت فوضوية.

لنفرض أنك كتبت برنامج ملاحة للمسافرين والسياح يدور حول خريطة جميلة تساعدة المستخدمين على توجيه أنفسهم بسرعة في أي مدينة. وقد وجدت أن إحدى أكثر المزايا طلبًا من المستخدمين هي الإنشاء الآلي للمسار بحيث يدخل المستخدم العنوان ليرى أسرع مسار إليه معروضًا على الخريطة. وفي أول إصدار لم يدعم البرنامج إنشاء المسارات سوى للمسارات التي على الشوارع فقط، وقد أسعد ذلك المستخدمين المسافرين بسياراتهم، ثم في التحديث التالي أضفت دعم إنشاء المسارات على طرق المشاة، ثم في الذي يليه أضفت إنشاء المسارات بالمواصلات العامة. ووجدت نفسك بعد ذلك تخطط لإضافة إمكانية إنشاء مسارات للدراجين، وبعده إمكانية إنشاء مسارات آلية لكل المزارات السياحية في المدينة.

ورغم أن البرنامج ناجح من المنظور التجاري، إلا أن الجزء الفني سبب لك متاعب إذ يتضاعف حجم الفئة الرئيسية للمستكشف كلما أضفت خوارزمية مسار جديدة، إلى أن تصل إلى نقطة لا يمكن صيانة الشيفرة فيها أو تعديلها. ذلك أن أي تغيير في إحدى الخوارزميات سواء كان مجرد تصحيح بسيط أو تعديل طفيف في درجة شارع ما (Street score) سيؤثر في الفئة كلها مما يزيد فرصة حدوث خطأ داخل الشيفرة العاملة. إضافة إلى هذا فإن العمل الجماعي صار غير مريح، فزملاءك الذين وظفتهم مع أول إصدارة ناجحة صاروا يشتكون أنهم ينفقون وقتًا طويلًا في حل مشاكل الدمج (merge conflicts)، وصار إدخال مزايا جديدة يتطلب منك تغيير نفس الفئة الضخمة تلك، مما يعارض الشيفرة التي كتبها غيرك.

الحل

(ش.2) استراتيجيات تخطيط المسارات

يقترح نمط الخطة أن تأخذ فئة تنفذ شيئًا محددًا بطرق كثيرة مختلفة، ثم تستخرج خوارزمياتها إلى فئات منفصلة تدعى الاستراتيجيات أو الخطط (strategies). ويجب أن تحتوي الفئة الأصلية التي تدعى بالسياق (context) على حقل لتخزين مرجع لواحدة من الخطط، ويفوض السياق العمل إلى كائن الخطة المرتبط به بدلًا من تنفيذه بنفسه. ولا يكون السياق مسؤولًا عن اختيار خوارزمية مناسبة للمهمة، بل يمرر العميلُ الخطةَ المرغوب فيها إلى السياق، وفي الواقع فإن السياق لا يعرف الكثير عن الخطط، فهو يعمل معها جميعًا من خلال نفس الواجهة العامة التي تكشف أسلوبًا واحدًا فقط لبدء الخوارزمية المغلفة داخل الخطة المختارة. ويصبح السياق بتلك الطريقة مستقلًا عن الخطط الحقيقية، لذا تستطيع إضافة خوارزميات جديدة أو تعدل الموجودة فعلًا دون تغيير شيفرة السياق أو الخطط الأخرى.

يمكن أن تُستخرج كل خوارزمية تخطيط لمسار داخل البرنامج في مثالنا إلى فئتها الخاصة مع أسلوب buildRoute، ويقبل الأسلوب مكان بداية ووجهة ثم يعيد مجموعة من النقاط المرحلية (checkpoints) على المسار. وكل فئةِ تحديدِ مسارٍ يمكنها بناء مسار مختلف رغم إعطائها نفس الوسائط (arguments) إلا أن فئة المستكشف الأساسية لا تهتم أي خوارزمية تم اختيارها طالما أن مهمتها الأساسية هي إخراج مجموعة نقاط مرحلية على الخريطة. والفئة بها أسلوب لتبديل استراتيجية التوجيه النشطة كي يتمكم عملاؤها -كالأزرار في واجهة المستخدم- من تبديل سلوك التوجيه الحالي بواحد غيره.

مثال واقعي

استراتيجيات مختلفة للوصول إلى المطار

تخيل أنك تريد الوصول إلى المطار، يمكنك الوصول بالحافلة أو التاكسي أو حتى على الدراجة، تلك خطط تنقلك، وتستطيع اختيار إحداها وفقًا لعوامل مثل الميزانية أو الوقت.

البنية

  1. يحتفظ السياق Context بمرجع إلى إحدى الخطط الحقيقية ويتواصل مع هذا الكائن من خلال واجهة الخطة فقط.
  2. تكون واجهة الخطة Strategy مشتركة لكل الخطط الحقيقية، وتصرح عن أسلوب يستخدمه السياق لتنفيذ الخطط.
  3. تستخدم الخطط الحقيقية Concrete Strategies أشكالًا مختلفة من الخوارزمية التي يستخدمها السياق.
  4. يستدعي السياق أسلوب التنفيذ على كائن الخطة المرتبط به في كل مرة يحتاج إلى تشغيل الخوارزمية، ولا يعرف السياق أي نوع من الخطط يعمل معه أو كيف تُنفًّذ الخوارزمية.
  5. ينشئ العميل Client كائن خطة محدد ويمرره إلى السياق، ويكشف السياق محدِّدًا يسمح للعملاء باستبدال الخطة المرتبطة بالسياق أثناء وقت التشغيل.

مثال توضيحي

يستخدم السياق في هذا المثال عدة خطط لتنفيذ عدة عمليات حسابية.

// تصرح واجهة الخطة عن العمليات المشتركة لكل الإصدارات المدعومة لخوارزمية ما
// ويستخدم السياق تلك الواجهة لاستدعاء الخوارزمية التي تحددها الخطط
// الحقيقية.
interface Strategy is
    method execute(a, b)

// تطبق الخطط الحقيقيةُ الخوارزميةَ مع اتباع واجهة الخطة الأساسية.
// داخل السياق interchangeable وتجعل الواجهة تلك الخطط تبادلية.\
class ConcreteStrategyAdd implements Strategy is
    method execute(a, b) is
        return a + b

class ConcreteStrategySubtract implements Strategy is
    method execute(a, b) is
        return a - b

class ConcreteStrategyMultiply implements Strategy is
    method execute(a, b) is
        return a * b

// يحدد السياق الواجهة التي يطلبها العملاء.
class Context is
    // يحافظ السياق على مرجع لأحد كائنات الخطط، ولا يعرف الفئة الحقيقية للخطة.
    // ينبغي أن يعمل السياق مع جميع الخطط من خلال واجهة الخطة.
    private strategy: Strategy

    // يقبل السياق في العادة خطةً من خلال المنشئ، ويوفر محدِّدًا 
    // كي يمكن تبديل الخطة أثناء التشغيل.
    method setStrategy(Strategy strategy) is
        this.strategy = strategy

    // يفوض السياق بعض العمل إلى كائن الخطة بدلًا من استخدام عدة إصدارات 
    // للخوارزمية بنفسه.
    method executeStrategy(int a, int b) is
        return strategy.execute(a, b)


// تلتقط شيفرة العميل خطة حقيقية وتمررها إلى السياق، وينبغي أن يكون
// العميل مدركًا للفروق بين الخطط من أجل اتخاذ القرار المناسب .
class ExampleApplication is
    method main() is
        Create context object.

        Read first number.
        Read last number.
        Read the desired action from user input.

        if (action == addition) then
            context.setStrategy(new ConcreteStrategyAdd())

        if (action == subtraction) then
            context.setStrategy(new ConcreteStrategySubtract())

        if (action == multiplication) then
            context.setStrategy(new ConcreteStrategyMultiply())

        result = context.executeStrategy(First number, Second number)

        Print result.

قابلية التطبيق

  • استخدم نمط الخطة حين تريد استخدام عدة صور من خوارزمية داخل كائن وتكون قادرًا على التبديل من خوارزمية لأخرى أثناء التشغيل.

يسمح لك نمط الخطة بتغيير سلوك الكائن أثناء التشغيل بشكل غير مباشر من خلال ربطه بكائنات فرعية مختلفة تستطيع تنفيذ مهام فرعية محددة بطرق مختلفة.

  • استخدم نمط الخطة حين يكون لديك فئات كثيرة متشابهة لا تختلف إلا في طريقة تنفيذ سلوك ما.

يسمح لك نمط الخطة باستخراج السلوك المتغير إلى هرمية فئات منفصلة وجمع الفئات الأصلية في فئة واحدة، ومن ثم تقليل الشيفرة المكررة.

  • استخدم النمط لعزل منطق العمل لفئة ما عن تفاصيل تطبيق الخوارزمية الذي قد لا يكون مهمًا بقدر سياق المنطق نفسه.

يسمح لك نمط الخطة بعزل الشيفرة والبيانات الداخلية واعتماديات العديد من الخوارزميات من بقية الشيفرة، ويحصل عدة عملاء على واجهة بسيطة لتنفيذ الخوارزميات والتبديل بينهم أثناء التشغيل.

  • استخدم النمط حين تحتوي فئتك على معامِل شرطي كبير يبدل بين الأشكال المختلفة لنفس الخوارزمية.

يسمح لك نمط الخطة باستخدام شَرطية كتلك من خلال استخراج جميع الخوارزميات إلى فئات منفصلة، تطبق جميعها نفس الواجهة، ويفوض الكائن الأصلي التنفيذ إلى أحد تلك الكائنات بدلًا من تطبيق كل أشكال الخوارزمية.

كيفية الاستخدام

  1. داخل فئة السياق، حدد الخوارزمية التي تميل إلى التغير، وقد تكون شرطية كبيرة تختار وتنفذ شكلًا من نفس الخوارزمية أثناء التشغيل.
  2. صرح عن واجهة الخطة المشتركة لجميع أشكال الخوارزمية.
  3. استخرج جميع الخوارزميات واحدة واحدة إلى فئاتها الخاصة، يجب أن تستخدم تلك الخوارزميات واجهة الخطة.
  4. داخل فئة السياق، أضف حقلًا لتخزين مرجع إلى كائن خطة، ووفر محدِّدًا لاستبدال قيم ذلك الحقل. يجب أن يعمل السياق مع كائن الخطة من خلال واجهة الخطة فقط. وقد يحدد السياق واجهة تسمح للخطة بالوصول إلى بياناته.
  5. يجب أن يربط عملاء السياق بين السياق وبين خطة مناسبة تطابق الكيفية التي يتوقعون السياق أن ينفذ مهمته عليها.

المزايا والعيوب

المزايا

  • تستطيع تبديل الخوارزميات المستخدمة داخل كائن أثناء التشغيل.
  • تستطيع عزل تفاصيل الاستخدام لخوارزمية من الشيفرة التي تستخدمها.
  • تستطيع تبديل الاكتساب inheritance وإحلال التركيب Compositing مكانه.
  • مبدأ المفتوح/المغلق. تستطيع إدخال خطط جديدة دون الحاجة إلى تغيير السياق.

العيوب

  • إن لم يكن لديك سوى بضعة خوارزميات لا تتغير إلا نادرًا فليست هناك حاجة إلى تعقيد البرنامج بفئات جديدة وواجهات تأتي مع النمط.
  • يجب أن تدرك العملاء الاختلافات بين الخطط لتستطيع اختيار واحدة مناسبة.
  • تدعم كثير من لغات البرمجة الحديثة نوع الدالة الذي يسمح لك باستخدام إصدارات مختلفة من خوارزمية داخل مجموعة دوال مجهولة، ثم يمكنك استخدام تلك الدوال كما استخدمت كائنات الخطة تمامًا، لكن دون تضخيم شيفرتك بفئات وواجهات إضافية.

العلاقات مع الأنماط الأخرى

  • تتشابه أنماط الجسر والحالة والخطة (والمحول إلى حد ما) في بنياتها، فكل هذه الأنماط مبنية على التركيب (composition) الذي يفوض المهام إلى كائنات أخرى، لكنها كذلك تحل مشاكل مختلفة، فالنمط ليس وصفة لهيكلة شيفرتك بشكل معين، فهو كذلك يوصل المشكلة التي يحلها إلى المطورين الآخرين.
  • قد يبدو نمط الأمر مشابهًا لنمط الخطة (Strategy) إذ تستطيع استخدام كليهما لوصف كائن بإجراء ما، لكن هدف كل منهما يختلف كما يلي:
    • تستطيع استخدام نمط الأمر لتحويل أي أمرٍ (command) إلى كائن، وتصبح معامِلات العملية حقولًا لذلك الكائن. ويسمح لك هذا التحويل بتأجيل تنفيذ العملية أو وضعها في صف أو تخزين سجل الأوامر أو إرسال الأوامر إلى خدمات بعيدة (Remote Services)، إلخ
    • من الناحية الأخرى، يصف نمط الخطة (Strategy) عادة طرقًا مختلفة لتنفيذ نفس الشيء مما يسمح لك بتبديل تلك الخوارزميات داخل فئة سياقية واحدة.
  • يسمح لك المزخرف بتغيير سمة الكائن، بينما يسمح لك نمط الخطة بتغيير محتواه.
  • يبنى نمط أسلوب القالب على الاكتساب، إذ يسمح لك بتغيير أجزاء من خوارزمية من خلال توسيع أجزائه في فئات فرعية، أما نمط الخطة فيبنى على التركيب، فيمكنك تغيير أجزاء من سلوك الكائن من خلال إمداده بخطط مختلفة متوافقة لذلك السلوك.

ويعمل أسلوب القالب على مستوى الفئة لذا يُعد نمطًا ساكنًا (static)، بينما يعمل نمط الخطة على مستوى الكائن مما يسمح لك بتبديل السلوك أثناء التشغيل.

  • يمكن النظر إلى نمط الحالة على أنه امتداد لنمط الخطة، فكلاهما مبنيان على التركيب إذ يغيران سلوك السياق من خلال تفويض بعض المهام إلى كائناتٍ مساعدة، فيجعل نمط الخطة تلك الكائنات مستقلة تمامًا وغير مدركة لوجود بعضها البعض، أما نمط الحالة فلا يقيد الاعتماديات بين الحالات الحقيقية (concrete states) مما يسمح لهم بتغيير حالة السياق في أي وقت.

الاستخدام في لغة جافا

المستوى: ★ ☆ ☆

الانتشار:  ★ ★ ★

أمثلة الاستخدام:يكثر استخدام نمط الخطة في لغة جافا، إذ يستخدم عادة في أطر عمل عديدة لتزويد المستخدمين بطريقة لتغيير سلوك الفئة دون توسيعها. وقد دعمت جافا ثمان دوال من دوال lambda التي يمكن أن تكون بدائل أبسط لنمط الخطة. إليك بعض الأمثلة على نمط الخطة في مكتبات جافا:

يمكن ملاحظة نمط الخطة من خلال أسلوب يسمح للكائنات المتداخلة أن تنفذ المهام الحقيقية إضافة إلى محدِّد (setter) يسمح باستبدال ذلك الكائن بغيره.

الاستخدام في لغة #C

المستوى: ★ ☆ ☆

الانتشار:  ★ ★ ★

أمثلة الاستخدام: يكثر استخدام نمط الخطة في لغة #C، إذ يستخدم عادة في أطر عمل عديدة لتزويد المستخدمين بطريقة لتغيير سلوك الفئة دون توسيعها.

يمكن ملاحظة نمط الخطة من خلال أسلوب يسمح للكائنات المتداخلة أن تنفذ المهام الحقيقية إضافة إلى محدِّد (setter) يسمح باستبدال ذلك الكائن بغيره.

مثال تصوري

يوضح هذا المثال بنية نمط الخطة (Strategy)، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

الاستخدام في لغة PHP

المستوى: ★ ☆ ☆

الانتشار:  ★ ★ ★

أمثلة الاستخدام: يكثر استخدام نمط الخطة في لغة PHP، إذ يستخدم عادة في أطر عمل عديدة لتزويد المستخدمين بطريقة لتغيير سلوك الفئة دون توسيعها.

يمكن ملاحظة نمط الخطة من خلال أسلوب يسمح للكائنات المتداخلة أن تنفذ المهام الحقيقية إضافة إلى محدِّد (setter) يسمح باستبدال ذلك الكائن بغيره.

مثال تصوري

يوضح هذا المثال بنية نمط الخطة (Strategy)، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

سيكون من السهل عليك استيعاب المثال التالي بعد تعلم بنية النمط، بناء على استخدام واقعي له في لغة PHP.

الاستخدام في لغة بايثون

المستوى: ★ ☆ ☆

الانتشار:  ★ ★ ★

أمثلة الاستخدام: يكثر استخدام نمط الخطة في لغة بايثون، إذ يستخدم عادة في أطر عمل عديدة لتزويد المستخدمين بطريقة لتغيير سلوك الفئة دون توسيعها.

يمكن ملاحظة نمط الخطة من خلال أسلوب يسمح للكائنات المتداخلة أن تنفذ المهام الحقيقية إضافة إلى محدِّد (setter) يسمح باستبدال ذلك الكائن بغيره.

مثال تصوري

يوضح هذا المثال بنية نمط الخطة (Strategy)، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

الاستخدام في لغة روبي

المستوى: ★ ☆ ☆

الانتشار:  ★ ★ ★

أمثلة الاستخدام: يكثر استخدام نمط الخطة في لغة روبي، إذ يستخدم عادة في أطر عمل عديدة لتزويد المستخدمين بطريقة لتغيير سلوك الفئة دون توسيعها.

يمكن ملاحظة نمط الخطة من خلال أسلوب يسمح للكائنات المتداخلة أن تنفذ المهام الحقيقية إضافة إلى محدِّد (setter) يسمح باستبدال ذلك الكائن بغيره.

مثال تصوري

يوضح هذا المثال بنية نمط الخطة (Strategy)، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

الاستخدام في لغة Swift

المستوى: ★ ☆ ☆

الانتشار:  ★ ★ ★

أمثلة الاستخدام: يكثر استخدام نمط الخطة في لغة Swift، إذ يستخدم عادة في أطر عمل عديدة لتزويد المستخدمين بطريقة لتغيير سلوك الفئة دون توسيعها.

يمكن ملاحظة نمط الخطة من خلال أسلوب يسمح للكائنات المتداخلة أن تنفذ المهام الحقيقية إضافة إلى محدِّد (setter) يسمح باستبدال ذلك الكائن بغيره.

مثال تصوري

يوضح هذا المثال بنية نمط الخطة (Strategy)، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

بعد تعلم بنية النمط سيكون من السهل عليك استيعاب المثال التالي المبني على حالة واقعية في لغة Swift.

Example.swift: Conceptual Example

import XCTest

/// The Context defines the interface of interest to clients.
class Context {

    /// The Context maintains a reference to one of the Strategy objects. The
    /// Context does not know the concrete class of a strategy. It should work
    /// with all strategies via the Strategy interface.
    private var strategy: Strategy

    /// Usually, the Context accepts a strategy through the constructor, but
    /// also provides a setter to change it at runtime.
    init(strategy: Strategy) {
        self.strategy = strategy
    }

    /// Usually, the Context allows replacing a Strategy object at runtime.
    func update(strategy: Strategy) {
        self.strategy = strategy
    }

    /// The Context delegates some work to the Strategy object instead of
    /// implementing multiple versions of the algorithm on its own.
    func doSomeBusinessLogic() {
        print("Context: Sorting data using the strategy (not sure how it'll do it)\n")

        let result = strategy.doAlgorithm(["a", "b", "c", "d", "e"])
        print(result.joined(separator: ","))
    }
}

/// The Strategy interface declares operations common to all supported versions
/// of some algorithm.
///
/// The Context uses this interface to call the algorithm defined by Concrete
/// Strategies.
protocol Strategy {

    func doAlgorithm<T: Comparable>(_ data: [T]) -> [T]
}

/// Concrete Strategies implement the algorithm while following the base
/// Strategy interface. The interface makes them interchangeable in the Context.
class ConcreteStrategyA: Strategy {

    func doAlgorithm<T: Comparable>(_ data: [T]) -> [T] {
        return data.sorted()
    }
}

class ConcreteStrategyB: Strategy {

    func doAlgorithm<T: Comparable>(_ data: [T]) -> [T] {
        return data.sorted(by: >)
    }
}

/// Let's see how it all works together.
class StrategyConceptual: XCTestCase {

    func test() {

        /// The client code picks a concrete strategy and passes it to the
        /// context. The client should be aware of the differences between
        /// strategies in order to make the right choice.

        let context = Context(strategy: ConcreteStrategyA())
        print("Client: Strategy is set to normal sorting.\n")
        context.doSomeBusinessLogic()

        print("\nClient: Strategy is set to reverse sorting.\n")
        context.update(strategy: ConcreteStrategyB())
        context.doSomeBusinessLogic()
    }
}

Output.txt: Execution result

Client: Strategy is set to normal sorting.

Context: Sorting data using the strategy (not sure how it'll do it)

a,b,c,d,e

Client: Strategy is set to reverse sorting.

Context: Sorting data using the strategy (not sure how it'll do it)

e,d,c,b,a

مثال واقعي

Example.swift: Real World Example

import XCTest

class StrategyRealWorld: XCTestCase {

    /// This example shows a simple implementation of a list controller that is
    /// able to display models from different data sources:
    ///
    /// (MemoryStorage, CoreDataStorage, RealmStorage)

    func test() {

        let controller = ListController()

        let memoryStorage = MemoryStorage<User>()
        memoryStorage.add(usersFromNetwork())

        clientCode(use: controller, with: memoryStorage)

        clientCode(use: controller, with: CoreDataStorage())

        clientCode(use: controller, with: RealmStorage())
    }

    func clientCode(use controller: ListController, with dataSource: DataSource) {

        controller.update(dataSource: dataSource)
        controller.displayModels()
    }

    private func usersFromNetwork() -> [User] {
        let firstUser = User(id: 1, username: "username1")
        let secondUser = User(id: 2, username: "username2")
        return [firstUser, secondUser]
    }
}

class ListController {

    private var dataSource: DataSource?

    func update(dataSource: DataSource) {
        /// ... resest current states ...
        self.dataSource = dataSource
    }

    func displayModels() {

        guard let dataSource = dataSource else { return }
        let models = dataSource.loadModels() as [User]

        /// Bind models to cells of a list view...
        print("\nListController: Displaying models...")
        models.forEach({ print($0) })
    }
}

protocol DataSource {

    func loadModels<T: DomainModel>() -> [T]
}

class MemoryStorage<Model>: DataSource {

    private lazy var items = [Model]()

    func add(_ items: [Model]) {
        self.items.append(contentsOf: items)
    }

    func loadModels<T: DomainModel>() -> [T] {
        guard T.self == User.self else { return [] }
        return items as! [T]
    }
}

class CoreDataStorage: DataSource {

    func loadModels<T: DomainModel>() -> [T] {
        guard T.self == User.self else { return [] }

        let firstUser = User(id: 3, username: "username3")
        let secondUser = User(id: 4, username: "username4")

        return [firstUser, secondUser] as! [T]
    }
}

class RealmStorage: DataSource {

    func loadModels<T: DomainModel>() -> [T] {
        guard T.self == User.self else { return [] }

        let firstUser = User(id: 5, username: "username5")
        let secondUser = User(id: 6, username: "username6")

        return [firstUser, secondUser] as! [T]
    }
}

protocol DomainModel {

    var id: Int { get }
}

struct User: DomainModel {

    var id: Int
    var username: String
}

Output.txt: Execution result

ListController: Displaying models...
User(id: 1, username: "username1")
User(id: 2, username: "username2")

ListController: Displaying models...
User(id: 3, username: "username3")
User(id: 4, username: "username4")

ListController: Displaying models...
User(id: 5, username: "username5")
User(id: 6, username: "username6")

الاستخدام في لغة TypeScript

المستوى: ★ ☆ ☆

الانتشار:  ★ ★ ★

أمثلة الاستخدام: يكثر استخدام نمط الخطة في لغة TypeScript، إذ يستخدم عادة في أطر عمل عديدة لتزويد المستخدمين بطريقة لتغيير سلوك الفئة دون توسيعها.

يمكن ملاحظة نمط الخطة من خلال أسلوب يسمح للكائنات المتداخلة أن تنفذ المهام الحقيقية إضافة إلى محدِّد (setter) يسمح باستبدال ذلك الكائن بغيره.

مثال تصوري

يوضح هذا المثال بنية نمط الخطة (Strategy)، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

index.ts: Conceptual Example

/**
 * The Context defines the interface of interest to clients.
 */
class Context {
    /**
     * @type {Strategy} The Context maintains a reference to one of the Strategy
     * objects. The Context does not know the concrete class of a strategy. It
     * should work with all strategies via the Strategy interface.
     */
    private strategy: Strategy;

    /**
     * Usually, the Context accepts a strategy through the constructor, but also
     * provides a setter to change it at runtime.
     */
    constructor(strategy: Strategy) {
        this.strategy = strategy;
    }

    /**
     * Usually, the Context allows replacing a Strategy object at runtime.
     */
    public setStrategy(strategy: Strategy) {
        this.strategy = strategy;
    }

    /**
     * The Context delegates some work to the Strategy object instead of
     * implementing multiple versions of the algorithm on its own.
     */
    public doSomeBusinessLogic(): void {
        // ...

        console.log('Context: Sorting data using the strategy (not sure how it\'ll do it)');
        const result = this.strategy.doAlgorithm(['a', 'b', 'c', 'd', 'e']);
        console.log(result.join(','));

        // ...
    }
}

/**
 * The Strategy interface declares operations common to all supported versions
 * of some algorithm.
 *
 * The Context uses this interface to call the algorithm defined by Concrete
 * Strategies.
 */
interface Strategy {
    doAlgorithm(data: string[]): string[];
}

/**
 * Concrete Strategies implement the algorithm while following the base Strategy
 * interface. The interface makes them interchangeable in the Context.
 */
class ConcreteStrategyA implements Strategy {
    public doAlgorithm(data: string[]): string[] {
        return data.sort();
    }
}

class ConcreteStrategyB implements Strategy {
    public doAlgorithm(data: string[]): string[] {
        return data.reverse();
    }
}

/**
 * The client code picks a concrete strategy and passes it to the context. The
 * client should be aware of the differences between strategies in order to make
 * the right choice.
 */
const context = new Context(new ConcreteStrategyA());
console.log('Client: Strategy is set to normal sorting.');
context.doSomeBusinessLogic();

console.log('');

console.log('Client: Strategy is set to reverse sorting.');
context.setStrategy(new ConcreteStrategyB());
context.doSomeBusinessLogic();

Output.txt: Execution result

Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
a,b,c,d,e

Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
e,d,c,b,a

انظر أيضًا

مصادر