نمط أسلوب القالب Temlate Method

من موسوعة حسوب
< Design Patterns
مراجعة 23:33، 27 سبتمبر 2019 بواسطة أسامه-دمراني (نقاش | مساهمات) (3.1 تمام التعديل على الصور.)
اذهب إلى التنقل اذهب إلى البحث

نمط أسلوب القالب Template Method هو نمط تصميم سلوكي يحدد هيكل الخوارزمية في الفئة الرئيسية (superclass) لكنه في نفس الوقت يسمح للفئات الفرعية بتغيير خطوات بعينها من تلك الخوارزمية دون تغيير هيكلها.

المشكلة

(ش.1) تحتوي فئات التنقيب عن البيانات على شيفرات مكررة كثيرة.

تخيل أنك تنشئ برنامج للتنقيب عن البيانات يحلل مستندات الشركات، ويغذي المستخدمون البرنامج بمستندات بصيغ متعددة (PDF-DOC-CSV)، ويحاول البرنامج استخراج بيانات ذات قيمة منها ويضعها في صيغة موحدة. سيعمل أول إصدار من البرنامج مع مستندات DOC فقط، والإصدار التالي سيكون قادرًا على دعم ملفات CSV، ثم في الشهر الذي يليه ستكون قد "علَّمتَه" أن يستخرج البيانات من ملفات PDF.

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

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

الحل

(ش.2) يقسم أسلوب القالب الخوارزمية إلى خطوات سامحًا للفئات الفرعية بتغيير تلك الخطوات لكنها لا تغير الأسلوب الفعلي.

يقترح نمط أسلوب القالب أن تقسم الخوارزمية إلى سلسلة من الخطوات ثم تحول تلك الخطوات إلى أساليب وتضع سلسلة من الاستدعاءات لتلك الأساليب داخل أسلوب قالب وحيد. وقد تكون تلك الخطوات مجردة abstract أو لها بعض التطبيقات.

ومن أجل استخدام تلك الخوارزمية ينبغي على العميل أن يوفر فئته الفرعية ويطبق كل الخطوات المجردة ويغير بعض الخطوات الاختيارية إن دعت الحاجة (لكن ليس أسلوب القالب نفسه).

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

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

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

لدينا هنا نوعان من الخطوات:

  • خطوات تجريدية يجب على كل فئة فرعية تطبيقها.
  • خطوات اختيارية بها بعض التطبيق الافتراضي (default implementation)، لكن يمكن تخطيها عند الحاجة.

هناك نوع آخر من الخطوات اسمه الخطاطيف (hooks)، وهي خطوات اختيارية، متنها (body) فارغ. وسيعمل أسلوب القالب حتى لو لم يتم تخطي الخطاف، وتوضع الخطاطيف عادة قبل أو بعد الخطوات الحرجة في الخوارزميات لتزود الفئات الفرعية بنقاط توسعة إضافية للخوارزمية.

مثال واقعي

(ش.3) يمكن تعديل خطة هندسية نموذجية لتناسب احتياجات العميل.

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

البنية

dptm.structure-indexed.png
  1. تصرح الفئة المجردة (Abstract Class) عن أساليب تتصرف كخطوات للخوارزمية، إضافة إلى أسلوب القالب الفعلي الذي يستدعي تلك الأساليب بترتيب محدد، ويصرَّح عن تلك الأساليب على أنها مجردة abstract أو لديها بعض التطبيق الافتراضي (default implementation).
  2. تستطيع الفئات الحقيقية (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) كما هي.

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

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

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

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

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

المزايا

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

العيوب

  • قد يكون بعض العملاء محدودًا بهيكل الخوارزمية الذي توفره له.
  • قد تخرق مبدأ استبدال ليزكوف (Liskov Substitution Principle) عبر منع التطبيق الافتراضي لخطوة ما من خلال فئة فرعية.
  • تميل أساليب القالب إلى أن تكون صعبة الصيانة والتعديل كلما زاد عدد الخطوات فيها.

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

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

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

المستوى: ★ ☆ ☆

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

أمثلة الاستخدام: يكثر استخدام نمط أسلوب القالب في أطر عمل لغة جافا، ويستخدمه المطورون عادة لتزويد مستخدمي أطر العمل بوسائل بسيط لتوسيع الوظيفية القياسية عبر الاكتساب (Inheritance).

إليك بعض الأمثلة على نمط أسلوب القالب في مكتبات جافا:

يمكن ملاحظة أسلوب القالب من خلال الأساليب السلوكية التي بها سلوك افتراضي تحدده الفئة الأساسية.

تخطي الخطوات القياسية للخوارزمية

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

الشبكات networks

 networks/Network.java: فئة الشبكة الاجتماعية الأساسية
package refactoring_guru.template_method.example.networks;

/**
 * الفئة الأساسية للشبكة الاجتماعية.
 */
public abstract class Network {
    String userName;
    String password;

    Network() {}

    /**
     * انشر البيانات للشبكة الاجتماعية أيًا كانت.
     */
    public boolean post(String message) {
        // قم بالتوثيق قبل النشر، إذ تستخدم كل شبكة اجتماعية
        // أسلوب استيثاق.
        if (logIn(this.userName, this.password)) {
            // أرسل بيانات المنشور.
            boolean result =  sendData(message.getBytes());
            logOut();
            return result;
        }
        return false;
    }

    abstract boolean logIn(String userName, String password);
    abstract boolean sendData(byte[] data);
    abstract void logOut();
}
 networks/Facebook.java: الشبكة الاجتماعية الحقيقية
package refactoring_guru.template_method.example.networks;

/**
 * فئة الشبكة الاجتماعية.
 */
public class Facebook extends Network {
    public Facebook(String userName, String password) {
        this.userName = userName;
        this.password = password;
    }

    public boolean logIn(String userName, String password) {
        System.out.println("\nChecking user's parameters");
        System.out.println("Name: " + this.userName);
        System.out.print("Password: ");
        for (int i = 0; i < this.password.length(); i++) {
            System.out.print("*");
        }
        simulateNetworkLatency();
        System.out.println("\n\nLogIn success on Facebook");
        return true;
    }

    public boolean sendData(byte[] data) {
        boolean messagePosted = true;
        if (messagePosted) {
            System.out.println("Message: '" + new String(data) + "' was posted on Facebook");
            return true;
        } else {
            return false;
        }
    }

    public void logOut() {
        System.out.println("User: '" + userName + "' was logged out from Facebook");
    }

    private void simulateNetworkLatency() {
        try {
            int i = 0;
            System.out.println();
            while (i < 10) {
                System.out.print(".");
                Thread.sleep(500);
                i++;
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}
 networks/Twitter.java: شبكة اجتماعية أخرى
package refactoring_guru.template_method.example.networks;

/**
 * فئة الشبكة الاجتماعية.
 */
public class Twitter extends Network {

    public Twitter(String userName, String password) {
        this.userName = userName;
        this.password = password;
    }

    public boolean logIn(String userName, String password) {
        System.out.println("\nChecking user's parameters");
        System.out.println("Name: " + this.userName);
        System.out.print("Password: ");
        for (int i = 0; i < this.password.length(); i++) {
            System.out.print("*");
        }
        simulateNetworkLatency();
        System.out.println("\n\nLogIn success on Twitter");
        return true;
    }

    public boolean sendData(byte[] data) {
        boolean messagePosted = true;
        if (messagePosted) {
            System.out.println("Message: '" + new String(data) + "' was posted on Twitter");
            return true;
        } else {
            return false;
        }
    }

    public void logOut() {
        System.out.println("User: '" + userName + "' was logged out from Twitter");
    }

    private void simulateNetworkLatency() {
        try {
            int i = 0;
            System.out.println();
            while (i < 10) {
                System.out.print(".");
                Thread.sleep(500);
                i++;
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}
 Demo.java: شيفرة العميل
package refactoring_guru.template_method.example;

import refactoring_guru.template_method.example.networks.Facebook;
import refactoring_guru.template_method.example.networks.Network;
import refactoring_guru.template_method.example.networks.Twitter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * فئة العرض، يجتمع كل شيء هنا.
 */
public class Demo {
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        Network network = null;
        System.out.print("Input user name: ");
        String userName = reader.readLine();
        System.out.print("Input password: ");
        String password = reader.readLine();

        // أدخل الرسالة.
        System.out.print("Input message: ");
        String message = reader.readLine();

        System.out.println("\nChoose social network for posting message.\n" +
                "1 - Facebook\n" +
                "2 - Twitter");
        int choice = Integer.parseInt(reader.readLine());

        // أنشئ كائن الشبكة المناسب وأرسل الرسالة.
        if (choice == 1) {
            network = new Facebook(userName, password);
        } else if (choice == 2) {
            network = new Twitter(userName, password);
        }
        network.post(message);
    }
}
 OutputDemo.txt: نتائج التنفيذ
Input user name: Jhonatan
Input password: qswe
Input message: Hello, World!

Choose social network for posting message.
1 - Facebook
2 - Twitter
2

Checking user's parameters
Name: Jhonatan
Password: ****
..........

LogIn success on Twitter
Message: 'Hello, World!' was posted on Twitter
User: 'Jhonatan' was logged out from Twitter

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

المستوى: ★ ☆ ☆

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

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

يمكن ملاحظة أسلوب القالب من خلال الأساليب السلوكية التي بها سلوك افتراضي تحدده الفئة الأساسية.

مثال تصوري

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

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

 Program.cs: مثال تصوري

using System;

namespace RefactoringGuru.DesignPatterns.TemplateMethod.Conceptual
{
    // تحدد الفئة المجردة أسلوب قالب يحتوي على هيكل لخوارزمية 
    // مكونة من استدعاءات تُستخدم عادة لتجريد عمليات 
    // وتستخدم الفئات الفرعية الحقيقية ، primitive operations أولية.
    //
    // يجب أن تطبق الفئات الحقيقية كل العمليات المجردة للفئة الأساسية
    // أسلوب القالب نفسه (override) لكن دون تخطي.
    abstract class AbstractClass
    {
        // يحدد أسلوب القالب هيكل الخوارزمية.
        public void TemplateMethod()
        {
            this.BaseOperation1();
            this.RequiredOperations1();
            this.BaseOperation2();
            this.Hook1();
            this.RequiredOperation2();
            this.BaseOperation3();
            this.Hook2();
        }

        // فعليًا implementations هذه العمليات لها تطبيقات
        protected void BaseOperation1()
        {
            Console.WriteLine("AbstractClass says: I am doing the bulk of the work");
        }

        protected void BaseOperation2()
        {
            Console.WriteLine("AbstractClass says: But I let subclasses override some operations");
        }

        protected void BaseOperation3()
        {
            Console.WriteLine("AbstractClass says: But I am doing the bulk of the work anyway");
        }
        
        // يجب أن تُطبَّق هذه العمليات في فئات فرعية.
        protected abstract void RequiredOperations1();

        protected abstract void RequiredOperation2();
        
        // هذه خطاطيف، قد تتخطاها الفئات الفرعية، لكن ذلك ليس شرطًا
        // بما أن الخطاطيف لديها تطبيق افتراضي لكنه فارغ.
        // توفر الخطاطيف نقاط توسعة إضافية في بعض الأماكن الحرجة
        // من الخوارزمية.
        protected virtual void Hook1() { }

        protected virtual void Hook2() { }
    }

    // يجب أن تطبق الفئات الحقيقية جميع العمليات المجردة للفئة الأساسية
    // كما تستطيع تخطي بعض العمليات واستبدالها بالتطبيق الافتراضي.
    class ConcreteClass1 : AbstractClass
    {
        protected override void RequiredOperations1()
        {
            Console.WriteLine("ConcreteClass1 says: Implemented Operation1");
        }

        protected override void RequiredOperation2()
        {
            Console.WriteLine("ConcreteClass1 says: Implemented Operation2");
        }
    }

    // عادة ما تتخطى الفئات الحقيقية جزءًا بسيطًا من عمليات الفئة الأساسية.
    class ConcreteClass2 : AbstractClass
    {
        protected override void RequiredOperations1()
        {
            Console.WriteLine("ConcreteClass2 says: Implemented Operation1");
        }

        protected override void RequiredOperation2()
        {
            Console.WriteLine("ConcreteClass2 says: Implemented Operation2");
        }

        protected override void Hook1()
        {
            Console.WriteLine("ConcreteClass2 says: Overridden Hook1");
        }
    }

    class Client
    {
        // تستدعي شيفرة العميل أسلوب القالب لتنفيذ الخوارزمية، وليس على 
        // شيفرة العميل أن تعرف الفئة الحقيقية للكائن الذي تعمل معه طالما
        // أنها تعمل مع الكائنات من خلال واجهة فئتها الأساسية.
        public static void ClientCode(AbstractClass abstractClass)
        {
            // ...
            abstractClass.TemplateMethod();
            // ...
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Same client code can work with different subclasses:");

            Client.ClientCode(new ConcreteClass1());

            Console.Write("\n");
            
            Console.WriteLine("Same client code can work with different subclasses:");
            Client.ClientCode(new ConcreteClass2());
        }
    }
}

 Output.txt: نتائج التنفيذ

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

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

المستوى: ★ ☆ ☆

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

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

مثال تصوري

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

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

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

 index.php: مثال تصوري

<?php

namespace RefactoringGuru\TemplateMethod\Conceptual;

/**
 * تحدد الفئة المجردة أسلوب قالب يحتوي على هيكل لخوارزمية 
 * مكونة من استدعاءات تُستخدم عادة لتجريد عمليات 
 * وتستخدم الفئات الفرعية الحقيقية ، primitive operations أولية.
 * 
 * يجب أن تطبق الفئات الحقيقية كل العمليات المجردة للفئة الأساسية
 * أسلوب القالب نفسه (override) لكن دون تخطي.
 */
abstract class AbstractClass
{
    /**
     * يحدد أسلوب القالب هيكل الخوارزمية.
     */
    final public function templateMethod(): void
    {
        $this->baseOperation1();
        $this->requiredOperations1();
        $this->baseOperation2();
        $this->hook1();
        $this->requiredOperation2();
        $this->baseOperation3();
        $this->hook2();
    }

    /**
     * فعليًا implementations هذه العمليات لها تطبيقات
     */
    protected function baseOperation1(): void
    {
        echo "AbstractClass says: I am doing the bulk of the work\n";
    }

    protected function baseOperation2(): void
    {
        echo "AbstractClass says: But I let subclasses override some operations\n";
    }

    protected function baseOperation3(): void
    {
        echo "AbstractClass says: But I am doing the bulk of the work anyway\n";
    }

    /**
     * يجب أن تُطبَّق هذه العمليات في فئات فرعية.
     */
    abstract protected function requiredOperations1(): void;

    abstract protected function requiredOperation2(): void;

    /**
     * هذه خطاطيف، قد تتخطاها الفئات الفرعية، لكن ذلك ليس شرطًا
     * بما أن الخطاطيف لديها تطبيق افتراضي لكنه فارغ.
     * توفر الخطاطيف نقاط توسعة إضافية في بعض الأماكن الحرجة
     * من الخوارزمية.
     */
    protected function hook1(): void { }

    protected function hook2(): void { }
}

/**
 * يجب أن تطبق الفئات الحقيقية جميع العمليات المجردة للفئة الأساسية
 * كما تستطيع تخطي بعض العمليات واستبدالها بالتطبيق الافتراضي.
 */
class ConcreteClass1 extends AbstractClass
{
    protected function requiredOperations1(): void
    {
        echo "ConcreteClass1 says: Implemented Operation1\n";
    }

    protected function requiredOperation2(): void
    {
        echo "ConcreteClass1 says: Implemented Operation2\n";
    }
}

/**
 * عادة ما تتخطى الفئات الحقيقية جزءًا بسيطًا من عمليات الفئة الأساسية.
 */
class ConcreteClass2 extends AbstractClass
{
    protected function requiredOperations1(): void
    {
        echo "ConcreteClass2 says: Implemented Operation1\n";
    }

    protected function requiredOperation2(): void
    {
        echo "ConcreteClass2 says: Implemented Operation2\n";
    }

    protected function hook1(): void
    {
        echo "ConcreteClass2 says: Overridden Hook1\n";
    }
}

/**
 * تستدعي شيفرة العميل أسلوب القالب لتنفيذ الخوارزمية، وليس على 
 * شيفرة العميل أن تعرف الفئة الحقيقية للكائن الذي تعمل معه طالما
 * أنها تعمل مع الكائنات من خلال واجهة فئتها الأساسية.
 */
function clientCode(AbstractClass $class)
{
    // ...
    $class->templateMethod();
    // ...
}

echo "Same client code can work with different subclasses:\n";
clientCode(new ConcreteClass1);
echo "\n";

echo "Same client code can work with different subclasses:\n";
clientCode(new ConcreteClass2);

 Output.txt: نتائج التنفيذ

Same client code can work with different subclasses:
AbstractClass says: I am doing bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses to override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing bulk of the work anyway

Same client code can work with different subclasses:
AbstractClass says: I am doing bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses to override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing bulk of the work anyway

مثال واقعي

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

 index.php: مثال واقعي

<?php

namespace RefactoringGuru\TemplateMethod\RealWorld;

/**
 * تحدد الفئة المجردة أسلوب القالب وتصرح عن جميع خطواتها.
 */
abstract class SocialNetwork
{
    protected $username;

    protected $password;

    public function __construct(string $username, string $password)
    {
        $this->username = $username;
        $this->password = $password;
    }

    /**
     * يستدعي أسلوب القالب الفعلي خطوات مجردة في ترتيب محدد، وقد تطبق 
     * الفئة المجردة جميع الخطوات سامحة لهذا الأسلوب أن ينشر شيئًا فعلًا
     * إلى الشبكة الاجتماعية.
     */
    public function post(string $message): bool
    {
        // قم بالتوثيق قبل النشرة، تستخدم كل شبكة اجتماعية
        // أسلوب استيثاق مختلف.
        if ($this->logIn($this->username, $this->password)) {
            // أرسل بيانات المنشور، جميع الشبكات لها واجهة برمجة 
            // مختلفة APIs تطبيقات.
            $result = $this->sendData($message);
            // ...
            $this->logOut();

            return $result;
        }

        return false;
    }

    /**
     * يُصرَّح عن جميع الخطوات على أنها مجردة لإجبار الفئات الفرعية على
     * تطبيقها جميعًا.
     */
    abstract public function logIn(string $userName, string $password): bool;

    abstract public function sendData(string $message): bool;

    abstract public function logOut(): void;
}

/**
 * الخاصة بفيس بوك، أو تتظاهر بذلك API تطبق هذه الفئة الحقيقية.
 * This Concrete Class implements the Facebook API (all right, it pretends to).
 */
class Facebook extends SocialNetwork
{
    public function logIn(string $userName, string $password): bool
    {
        echo "\nChecking user's credentials...\n";
        echo "Name: " . $this->username . "\n";
        echo "Password: " . str_repeat("*", strlen($this->password)) . "\n";

        simulateNetworkLatency();

        echo "\n\nFacebook: '" . $this->username . "' has logged in successfully.\n";

        return true;
    }

    public function sendData(string $message): bool
    {
        echo "Facebook: '" . $this->username . "' has posted '" . $message . "'.\n";

        return true;
    }

    public function logOut(): void
    {
        echo "Facebook: '" . $this->username . "' has been logged out.\n";
    }
}

/**
 * الخاصة بتويتر API تطبق هذه الفئة الحقيقية.
 */
class Twitter extends SocialNetwork
{
    public function logIn(string $userName, string $password): bool
    {
        echo "\nChecking user's credentials...\n";
        echo "Name: " . $this->username . "\n";
        echo "Password: " . str_repeat("*", strlen($this->password)) . "\n";

        simulateNetworkLatency();

        echo "\n\nTwitter: '" . $this->username . "' has logged in successfully.\n";

        return true;
    }

    public function sendData(string $message): bool
    {
        echo "Twitter: '" . $this->username . "' has posted '" . $message . "'.\n";

        return true;
    }

    public function logOut(): void
    {
        echo "Twitter: '" . $this->username . "' has been logged out.\n";
    }
}

/**
 * دالة مساعد صغيرة تجعل أوقات الانتظار تبدو حقيقية.
 */
function simulateNetworkLatency()
{
    $i = 0;
    while ($i < 5) {
        echo ".";
        sleep(1);
        $i++;
    }
}

/**
 * شيفرة العميل.
 */
echo "Username: \n";
$username = readline();
echo "Password: \n";
$password = readline();
echo "Message: \n";
$message = readline();

echo "\nChoose the social network to post the message:\n" .
    "1 - Facebook\n" .
    "2 - Twitter\n";
$choice = readline();

// والآن، لننشئ كائن شبكة اجتماعية مناسب وأرسل الرسالة.
if ($choice == 1) {
    $network = new Facebook($username, $password);
} elseif ($choice == 2) {
    $network = new Twitter($username, $password);
} else {
    die("Sorry, I'm not sure what you mean by that.\n");
}
$network->post($message);

 Output.txt: نتائج التنفيذ

Username:
> neo
Password:
> 123123
Message:
> What is the Matrix?

Choose the social network to post the message:
1 - Facebook
2 - Twitter
> 1

Checking user's credentials...
Name: neo
Password: ******
.....

Facebook: 'neo' has logged in successfully.
Facebook: 'neo' has posted 'What is the Matrix?'.
Facebook: 'neo' has been logged out.

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

المستوى: ★ ☆ ☆

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

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

يمكن ملاحظة أسلوب القالب من خلال الأساليب السلوكية التي بها سلوك افتراضي تحدده الفئة الأساسية.

مثال تصوري

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

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

 main.py: مثال تصوري

from abc import ABC, abstractmethod


class AbstractClass(ABC):
    """
    تحدد الفئة المجردة أسلوب قالب يحتوي على هيكل لخوارزمية
    مكونة من استدعاءات تُستخدم عادة لتجريد عمليات
    primitive operations أولية.

    وتستخدم الفئات الفرعية الحقيقية تلك العمليات دون المساس
    بأسلوب القالب نفسه.
    """

    def template_method(self) -> None:
        """
        يحدد أسلوب القالب هيكل الخوارزمية.
        """

        self.base_operation1()
        self.required_operations1()
        self.base_operation2()
        self.hook1()
        self.required_operations2()
        self.base_operation3()
        self.hook2()

    # فعليًا implementations هذه العمليات لها تطبيقات

    def base_operation1(self) -> None:
        print("AbstractClass says: I am doing the bulk of the work")

    def base_operation2(self) -> None:
        print("AbstractClass says: But I let subclasses override some operations")

    def base_operation3(self) -> None:
        print("AbstractClass says: But I am doing the bulk of the work anyway")

    # يجب أن تُطبَّق هذه العمليات في فئات فرعية.

    @abstractmethod
    def required_operations1(self) -> None:
        pass

    @abstractmethod
    def required_operations2(self) -> None:
        pass

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

    def hook1(self) -> None:
        pass

    def hook2(self) -> None:
        pass


class ConcreteClass1(AbstractClass):
    """
    يجب أن تطبق الفئات الحقيقية جميع العمليات المجردة للفئة الأساسية
    كما تستطيع تخطي بعض العمليات واستبدالها بالتطبيق الافتراضي.
    """

    def required_operations1(self) -> None:
        print("ConcreteClass1 says: Implemented Operation1")

    def required_operations2(self) -> None:
        print("ConcreteClass1 says: Implemented Operation2")


class ConcreteClass2(AbstractClass):
    """
    عادة ما تتخطى الفئات الحقيقية جزءًا بسيطًا من عمليات الفئة الأساسية.
    """

    def required_operations1(self) -> None:
        print("ConcreteClass2 says: Implemented Operation1")

    def required_operations2(self) -> None:
        print("ConcreteClass2 says: Implemented Operation2")

    def hook1(self) -> None:
        print("ConcreteClass2 says: Overridden Hook1")


def client_code(abstract_class: AbstractClass) -> None:
    """
    تستدعي شيفرة العميل أسلوب القالب لتنفيذ الخوارزمية، وليس على 
    شيفرة العميل أن تعرف الفئة الحقيقية للكائن الذي تعمل معه طالما
    أنها تعمل مع الكائنات من خلال واجهة فئتها الأساسية.
    """

    # ...
    abstract_class.template_method()
    # ...


if __name__ == "__main__":
    print("Same client code can work with different subclasses:")
    client_code(ConcreteClass1())
    print("")

    print("Same client code can work with different subclasses:")
    client_code(ConcreteClass2())

 Output.txt: نتائج التنفيذ

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

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

المستوى: ★ ☆ ☆

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

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

يمكن ملاحظة أسلوب القالب من خلال الأساليب السلوكية التي بها سلوك افتراضي تحدده الفئة الأساسية.

مثال تصوري

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

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

 main.rb: مثال تصوري

# تحدد الفئة المجردة أسلوب قالب يحتوي على هيكل لخوارزمية
# مكونة من استدعاءات تُستخدم عادة لتجريد عمليات 
# primitive operations أولية.
# 
# وتستخدم الفئات الفرعية الحقيقية تلك العمليات دون المساس
# بأسلوب القالب نفسه.
class AbstractClass
  # يحدد أسلوب القالب هيكل الخوارزمية.
  def template_method
    base_operation1
    required_operations1
    base_operation2
    hook1
    required_operations2
    base_operation3
    hook2
  end

  # فعليًا implementations هذه العمليات لها تطبيقات

  def base_operation1
    puts 'AbstractClass says: I am doing the bulk of the work'
  end

  def base_operation2
    puts 'AbstractClass says: But I let subclasses override some operations'
  end

  def base_operation3
    puts 'AbstractClass says: But I am doing the bulk of the work anyway'
  end

  # يجب أن تُطبَّق هذه العمليات في فئات فرعية.
  def required_operations1
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end

  # @abstract
  def required_operations2
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end

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

  def hook1; end

  def hook2; end
end

# يجب أن تطبق الفئات الحقيقية جميع العمليات المجردة للفئة الأساسية
# كما تستطيع تخطي بعض العمليات واستبدالها بالتطبيق الافتراضي.
class ConcreteClass1 < AbstractClass
  def required_operations1
    puts 'ConcreteClass1 says: Implemented Operation1'
  end

  def required_operations2
    puts 'ConcreteClass1 says: Implemented Operation2'
  end
end

# عادة ما تتخطى الفئات الحقيقية جزءًا بسيطًا من عمليات الفئة الأساسية.
class ConcreteClass2 < AbstractClass
  def required_operations1
    puts 'ConcreteClass2 says: Implemented Operation1'
  end

  def required_operations2
    puts 'ConcreteClass2 says: Implemented Operation2'
  end

  def hook1
    puts 'ConcreteClass2 says: Overridden Hook1'
  end
end

# تستدعي شيفرة العميل أسلوب القالب لتنفيذ الخوارزمية، وليس على 
# شيفرة العميل أن تعرف الفئة الحقيقية للكائن الذي تعمل معه طالما
# أنها تعمل مع الكائنات من خلال واجهة فئتها الأساسية.
def client_code(abstract_class)
  # ...
  abstract_class.template_method
  # ...
end

puts 'Same client code can work with different subclasses:'
client_code(ConcreteClass1.new)
puts "\n"

puts 'Same client code can work with different subclasses:'
client_code(ConcreteClass2.new)

 output.txt: نتائج التنفيذ

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

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

المستوى: ★ ☆ ☆

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

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

يمكن ملاحظة أسلوب القالب من خلال الأساليب السلوكية التي بها سلوك افتراضي تحدده الفئة الأساسية.

مثال تصوري

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

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

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

 Example.swift: مثال تصوري

import XCTest


/// يحدد البروتوكول المجرد وامتداده أسلوب قالب يحتوي هيكل خوارزمية ما
/// تتكون من استدعاءات إلى عمليات أولية مجردة في العادة.
///
/// يجب أن تطبق الفئات الفرعية تلك العمليات دون المساس بأسلوب
/// القالب نفسه.
protocol AbstractProtocol {

    /// يحدد أسلوب القالب هيكل الخوارزمية.
    func templateMethod()

    /// فعليًا implementations هذه العمليات لها تطبيقات
    func baseOperation1()

    func baseOperation2()

    func baseOperation3()

    /// يجب أن تُطبَّق هذه العمليات في فئات فرعية.
    func requiredOperations1()
    func requiredOperation2()

    /// هذه خطاطيف، قد تتخطاها الفئات الفرعية، لكن ذلك ليس شرطًا
    /// بما أن الخطاطيف لديها تطبيق افتراضي لكنه فارغ.
    /// توفر الخطاطيف نقاط توسعة إضافية في بعض الأماكن الحرجة
    /// من الخوارزمية.
    func hook1()
    func hook2()
}

extension AbstractProtocol {

    func templateMethod() {
        baseOperation1()
        requiredOperations1()
        baseOperation2()
        hook1()
        requiredOperation2()
        baseOperation3()
        hook2()
    }

    /// فعليًا implementations هذه العمليات لها تطبيقات
    func baseOperation1() {
        print("AbstractProtocol says: I am doing the bulk of the work\n")
    }

    func baseOperation2() {
        print("AbstractProtocol says: But I let subclasses override some operations\n")
    }

    func baseOperation3() {
        print("AbstractProtocol says: But I am doing the bulk of the work anyway\n")
    }

    func hook1() {}
    func hook2() {}
}

/// يجب أن تطبق الفئات الحقيقية جميع العمليات المجردة للفئة الأساسية
/// كما تستطيع تخطي بعض العمليات واستبدالها بالتطبيق الافتراضي.
class ConcreteClass1: AbstractProtocol {

    func requiredOperations1() {
        print("ConcreteClass1 says: Implemented Operation1\n")
    }

    func requiredOperation2() {
        print("ConcreteClass1 says: Implemented Operation2\n")
    }

    func hook2() {
        print("ConcreteClass1 says: Overridden Hook2\n")
    }
}

/// عادة ما تتخطى الفئات الحقيقية جزءًا بسيطًا من عمليات الفئة الأساسية.
class ConcreteClass2: AbstractProtocol {

    func requiredOperations1() {
        print("ConcreteClass2 says: Implemented Operation1\n")
    }

    func requiredOperation2() {
        print("ConcreteClass2 says: Implemented Operation2\n")
    }

    func hook1() {
        print("ConcreteClass2 says: Overridden Hook1\n")
    }
}

/// تستدعي شيفرة العميل أسلوب القالب لتنفيذ الخوارزمية، وليس على
/// شيفرة العميل أن تعرف الفئة الحقيقية للكائن الذي تعمل معه طالما
/// أنها تعمل مع الكائنات من خلال واجهة فئتها الأساسية.
class Client {
    // ...
    static func clientCode(use object: AbstractProtocol) {
        // ...
        object.templateMethod()
        // ...
    }
    // ...
}


/// لنرى كيف سيعمل كل ذلك.... .
class TemplateMethodConceptual: XCTestCase {

    func test() {

        print("Same client code can work with different subclasses:\n")
        Client.clientCode(use: ConcreteClass1())

        print("\nSame client code can work with different subclasses:\n")
        Client.clientCode(use: ConcreteClass2())
    }
}

 Output.txt: نتائج التنفيذ

Same client code can work with different subclasses:

AbstractProtocol says: I am doing the bulk of the work

ConcreteClass1 says: Implemented Operation1

AbstractProtocol says: But I let subclasses override some operations

ConcreteClass1 says: Implemented Operation2

AbstractProtocol says: But I am doing the bulk of the work anyway

ConcreteClass1 says: Overridden Hook2


Same client code can work with different subclasses:

AbstractProtocol says: I am doing the bulk of the work

ConcreteClass2 says: Implemented Operation1

AbstractProtocol says: But I let subclasses override some operations

ConcreteClass2 says: Overridden Hook1

ConcreteClass2 says: Implemented Operation2

AbstractProtocol says: But I am doing the bulk of the work anyway

مثال واقعي

 Example.swift: مثال واقعي

import XCTest
import AVFoundation
import CoreLocation
import Photos

class TemplateMethodRealWorld: XCTestCase {

    /// مثالًا جيدًا على أسلوب القالب UIViewController تُعد دورة حياة.

    func testTemplateMethodReal() {

        let accessors = [CameraAccessor(), MicrophoneAccessor(), PhotoLibraryAccessor()]

        accessors.forEach { item in
            item.requestAccessIfNeeded({ status in
                let message = status ? "You have access to " : "You do not have access to "
                print(message + item.description + "\n")
            })
        }
    }
}

class PermissionAccessor: CustomStringConvertible {

    typealias Completion = (Bool) -> ()

    func requestAccessIfNeeded(_ completion: @escaping Completion) {

        guard !hasAccess() else { completion(true); return }

        willReceiveAccess()

        requestAccess { status in
            status ? self.didReceiveAcesss() : self.didRejectAcesss()

            completion(status)
        }
    }

    func requestAccess(_ completion: @escaping Completion) {
        fatalError("Should be overridden")
    }

    func hasAccess() -> Bool {
        fatalError("Should be overridden")
    }

    var description: String { return "PermissionAccessor" }

    /// الخطاطيف
    func willReceiveAccess() {}

    func didReceiveAcesss() {}

    func didRejectAcesss() {}
}

class CameraAccessor: PermissionAccessor {

    override func requestAccess(_ completion: @escaping Completion) {
        AVCaptureDevice.requestAccess(for: .video) { status in
            return completion(status)
        }
    }

    override func hasAccess() -> Bool {
        return AVCaptureDevice.authorizationStatus(for: .video) == .authorized
    }

    override var description: String { return "Camera" }
}

class MicrophoneAccessor: PermissionAccessor {

    override func requestAccess(_ completion: @escaping Completion) {
        AVAudioSession.sharedInstance().requestRecordPermission { status in
            completion(status)
        }
    }

    override func hasAccess() -> Bool {
        return AVAudioSession.sharedInstance().recordPermission() == .granted
    }

    override var description: String { return "Microphone" }
}

class PhotoLibraryAccessor: PermissionAccessor {

    override func requestAccess(_ completion: @escaping Completion) {
        PHPhotoLibrary.requestAuthorization { status in
            completion(status == .authorized)
        }
    }

    override func hasAccess() -> Bool {
        return PHPhotoLibrary.authorizationStatus() == .authorized
    }

    override var description: String { return "PhotoLibrary" }

    override func didReceiveAcesss() {
        /// PhotoLibrary نريد تتبع كم عدد الأشخاص الذي يعطون صلاحية لمكتبة الصور
        print("PhotoLibrary Accessor: Receive access. Updating analytics...")
    }

    override func didRejectAcesss() {
        /// ... وكذلك عدد الأشخاص الذي رفضوا صلاحية الوصول.
        print("PhotoLibrary Accessor: Rejected with access. Updating analytics...")
    }
}

 Output.txt: نتائج التنفيذ

You have access to Camera

You have access to Microphone

PhotoLibrary Accessor: Rejected with access. Updating analytics...
You do not have access to PhotoLibrary

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

المستوى: ★ ☆ ☆

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

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

يمكن ملاحظة أسلوب القالب من خلال الأساليب السلوكية التي بها سلوك افتراضي تحدده الفئة الأساسية.

مثال تصوري

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

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

 index.ts: مثال تصوري

/**
 * تحدد الفئة المجردة أسلوب قالب يحتوي على هيكل لخوارزمية
 * مكونة من استدعاءات تُستخدم عادة لتجريد عمليات
 * primitive operations أولية.
 *
 * ويجب أن تستخدم الفئات الفرعية الحقيقية تلك العمليات دون المساس
 * بأسلوب القالب نفسه.
 */
abstract class AbstractClass {
    /**
     * يحدد أسلوب القالب هيكل الخوارزمية.
     */
    public templateMethod(): void {
        this.baseOperation1();
        this.requiredOperations1();
        this.baseOperation2();
        this.hook1();
        this.requiredOperation2();
        this.baseOperation3();
        this.hook2();
    }

    /**
     * فعليًا implementations هذه العمليات لها تطبيقات.
     */
    protected baseOperation1(): void {
        console.log('AbstractClass says: I am doing the bulk of the work');
    }

    protected baseOperation2(): void {
        console.log('AbstractClass says: But I let subclasses override some operations');
    }

    protected baseOperation3(): void {
        console.log('AbstractClass says: But I am doing the bulk of the work anyway');
    }

    /**
     * يجب أن تُطبَّق هذه العمليات في فئات فرعية.
     */
    protected abstract requiredOperations1(): void;

    protected abstract requiredOperation2(): void;

    /**
     * هذه خطاطيف، قد تتخطاها الفئات الفرعية، لكن ذلك ليس شرطًا
     * بما أن الخطاطيف لديها تطبيق افتراضي لكنه فارغ.
     * توفر الخطاطيف نقاط توسعة إضافية في بعض الأماكن الحرجة
     * من الخوارزمية.
     */
    protected hook1(): void { }

    protected hook2(): void { }
}

/**
 * يجب أن تطبق الفئات الحقيقية جميع العمليات المجردة للفئة الأساسية
 * كما تستطيع تخطي بعض العمليات واستبدالها بالتطبيق الافتراضي.
 */
class ConcreteClass1 extends AbstractClass {
    protected requiredOperations1(): void {
        console.log('ConcreteClass1 says: Implemented Operation1');
    }

    protected requiredOperation2(): void {
        console.log('ConcreteClass1 says: Implemented Operation2');
    }
}

/**
 * عادة ما تتخطى الفئات الحقيقية جزءًا بسيطًا من عمليات الفئة الأساسية.
 */
class ConcreteClass2 extends AbstractClass {
    protected requiredOperations1(): void {
        console.log('ConcreteClass2 says: Implemented Operation1');
    }

    protected requiredOperation2(): void {
        console.log('ConcreteClass2 says: Implemented Operation2');
    }

    protected hook1(): void {
        console.log('ConcreteClass2 says: Overridden Hook1');
    }
}

/**
 * تستدعي شيفرة العميل أسلوب القالب لتنفيذ الخوارزمية، وليس على 
 * شيفرة العميل أن تعرف الفئة الحقيقية للكائن الذي تعمل معه طالما
 * أنها تعمل مع الكائنات من خلال واجهة فئتها الأساسية.
 */
function clientCode(abstractClass: AbstractClass) {
    // ...
    abstractClass.templateMethod();
    // ...
}

console.log('Same client code can work with different subclasses:');
clientCode(new ConcreteClass1());
console.log('');

console.log('Same client code can work with different subclasses:');
clientCode(new ConcreteClass2());

 Output.txt: نتائج التنفيذ

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

انظر أيضًا

مصادر