نمط الجسر

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث

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

المشكلة

لنقل أن لديك فئة هندسية اسمها shape، وتلك الفئة لها زوج من الفئات الفرعية هما circle وsquare، وتريد توسيع هرمية تلك الفئة لتضيف الألوان على فئات تلك الأشكال الهندسية، فسيكون الحل التقليدي هنا أن تنشئ فئتين فرعيتين لفئة shape هما Red و Blue مثلًا.

لكن بما أن لديك فئتين فرعيتين من البداية، فستحتاج إلى إنشاء أربع تجميعات لتحتوي الاحتمالات الممكنة للأشكال وألوانها التي قد تأخذها، مثل BlueCircle و RedSquare. انظر (ش.1)

ضع الصورة ، يزيد عدد تجميعات الفئات مع إضافة المزيد من الأشكال الهندسية.

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

الحل

تحدث هذه المشكلة بسبب أننا نحاول توسيع فئات الشكل في بعديْن مستقليْن هما الهيئة (form) واللون، وهذه الحالة شائعة مع اكتساب الفئات (classes inheritance).

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

ضع الصورة. يمكنك تجنب انفجار هرمية فئة ما بتحويلها إلى هرميات متعددة مرتبطة ببعضها.

واتباع هذا المنظور يمكّننا من استخراج الشيفرة المتعلقة باللون إلى فئتها الخاصة مع فئتين فرعيتين هما Red و Blue، وعندها تحصل فئة Shape على حقل مرجعي (reference field) يشير إلى أحد كائنات الألوان. ويمكن لفئة Shape الآن أن تفوض أي عمل يتعلق بالألوان إلى فئتي Shape و Color. ومن هنا لن تتطلب إضافة ألوان جديدة تغييرَ هرمية الشكل، والعكس صحيح.

التجريد والتنفيذ(Abstraction and Implementation)

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

والتجريد (والذي هو الواجهة -interface- أيضًا) هو طبقة تحكم عالية المستوى لبعض الكيانات، ولا يفترض بتلك الطبقة أن تنفذ أي عمل حقيقي من تلقاء نفسها، بل يجب أن تفوض الأعمال إلى طبقة التنفيذ (والتي يطلق عليها المنصة أيضًا).

لاحظ أننا لا نتكلم عن الواجهات (interfaces) أو الفئات المجردة (abstract classes) في لغتك البرمجية، فتلك أمور مختلفة عما نتحدث عنه هنا، ذلك أنه عند الحديث عن التطبيقات الحقيقية فإن التجريد يمكن تمثيله بواجهة مستخدم رسومية GUIـ أما التنفيذ فقد يكون أي شيفرة نظام تشغيل (API) تستدعيها طبقة الواجهة الرسومية كاستجابة لتفاعلات المستخدم.

وعمومًا يمكنك توسيع مثل ذلك التطبيق في اتجاهين منفصلين:

توفير عدة واجهات رسومية (مثلًا، واجهات مخصصة للعملاء العاديين أو مخصصة للمدراء).

دعم عدة واجهات لبرمجة تطبيقات (APIs) كأن يكون قادرًا على إطلاق التطبيق في ويندوز ولينكس وماك.

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

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

تستطيع وضع نظام لتلك الفوضى باستخراج الشيفرة المتعلقة بتجميعات بعينها للواجهة-المنصة إلى فئات مستقلة منفصلة، لكن ستكتشف قريبًا أن هناك الكثير من تلك الفئات. ستنمو هرمية الفئة كثيرًا لأن إضافة واجهة رسومية جديدة أو دعم واجهة برمجة تطبيقات (API) مختلفة سيتطلب إنشاء فئات أكثر.

سنحاول حل هذه المشكلة باستخدام نمط الجسر الذي يقترح تقسيم الفئات إلى هرميتين مختلفتين:

التجريد (Abstraction): الواجهة الرسومية للتطبيق (App).

التنفيذ (Implementation): واجهات برمجة التطبيقات (APIs) الخاصة بنظام التشغيل.

ضع الصورة. إحدى الطرق لهيكلة تطبيق متعدد المنصات.

يتحكم كائن التجريد (abstraction object) في مظهر التطبيق ويفوض المهام الفعلية إلى كائن التنفيذ (implementation object) المربوط به، وتكون التنفيذات (implementations) المختلفة تبادلية طالما تتبع واجهة مشتركة، مما يجعل الواجهة الرسومية تعمل في ويندوز ولينكس معًا.

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

البُنية

ضع الصورة.

  1. يوفر التجريد (Abstraction) منطق تحكم عالي المستوى، إذ يعتمد على كائن التنفيذ (implementation object) للقيام بالمهام الفعلية منخفضة المستوى.
  2. يصرح التنفيذ (Implementation) عن الواجهة الشائعة لكل التنفيذات الحقيقية (Concrete Implementations)، ويتواصل أي تجريد مع كائن تنفيذ من خلال الأساليب التي صُرِّح بها هنا فقط. وقد يسرد التجريد نفس الأساليب أيضًا لكنه عادة يصرح عن بعض السلوكيات المعقدة التي تعتمد على صور مختلفة من العمليات الأولية التي يصرح عنها التنفيذ (implementation).
  3. تحتوي التنفيذات الحقيقية (Concrete Implementations) على شيفرات خاصة بالمنصات الموجه إليها البرنامج (Platform-specific).
  4. التجريدات المنقحة (refined abstractions) توفر صورًا مختلفة من منطق التحكم (control logic)، وتعمل مع التنفيذات المختلفة من خلال واجهة تنفيذ عامة (general implementation interface).
  5. عادة لا يهتم العميل إلا بالعمل مع التجريد، لكن وظيفته على أي حال هي ربط كائن التجريد مع أحد كائنات التنفيذ.

مثال توضيحي

يشرح هذا المثال كيف يُستخدم نمط الجسر لتقسيم الشيفرة الأحادية لبرنامج يدير بعض الأجهزة الإلكترونية وأجهزة التحكم فيها عن بعد (remote controls)، ويمثل التنفيذَ هنا فئاتُ Device، بينما يمثل التجريدَ فئاتُ Remote.

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

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

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

تربط شيفرة العميل النوع الذي تريده من المتحكمات مع كائن جهاز محدد من خلال منشئ المتحكم (remote constructor).

// يحدد التجريد واجهة الجزء الخاص بالتحكم في هرمية الفئتين.
// ويحافظ على مرجع لكائن من هرمية التنفيذ ويفوض كل المهام الحقيقية إليه.

class RemoteControl is
    protected field device: Device
    constructor RemoteControl(device: Device) is
        this.device = device
    method togglePower() is
        if (device.isEnabled()) then
            device.disable()
        else
            device.enable()
    method volumeDown() is
        device.setVolume(device.getVolume() - 10)
    method volumeUp() is
        device.setVolume(device.getVolume() + 10)
    method channelDown() is
        device.setChannel(device.getChannel() - 1)
    method channelUp() is
        device.setChannel(device.getChannel() + 1)


// تستطيع زيادة الفئات من هرمية التجريد بشكل مستقل عن فئات الأجهزة.
class AdvancedRemoteControl extends RemoteControl is
    method mute() is
        device.setVolume(0)


// تصرح واجهة التنفيذ عن أساليب شائعة بين كل فئات التنفيذ الحقيقية، وليس
// ضروريًا أن تطابق واجهة التجريد، بل إنه قد تختلف واجهتين تمامًا.
//  وتوفر واجهة التنفيذ عمليات أولية فقط، بينما يحدد التجريد عمليات ذات
// المستوى الأعلى بناءً على تلك الأولية.
interface Device is
    method isEnabled()
    method enable()
    method disable()
    method getVolume()
    method setVolume(percent)
    method getChannel()
    method setChannel(channel)


// كل الأجهزة تتبع نفس الواجهة.
class Tv implements Device is
    // ...

class Radio implements Device is
    // ...


// في مكان ما من شيفرة العميل.
tv = new Tv()
remote = new RemoteControl(tv)
remote.togglePower()

radio = new Radio()
remote = new AdvancedRemoteControl(radio)

قابلية الاستخدام

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

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

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

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

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

استخدم الجسر إن كنت تريد أن تكون قادرًا على تبديل التنفيذات في وقت التشغيل.

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

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

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

  1. حدد الأبعاد المتعامدة في فئاتك، قد تكون تلك التصاميم المستقلة أحد ما يلي: تجريد/منصة، نطاق/بنية تحتية، واجهة أمامية (front-end)/واجهة خلفية (back-end)، أو واجهة/تنفيذ (implementation).
  2. انظر أي العمليات يحتاجها العميل وحددها في فئة التجرية الأساسية.
  3. حدد العمليات المتاحة لكل المنصات، وصرح عن العمليات التي يحتاجها التجريد في واجهة الاستخدام العامة.
  4. أنشئ فئات تنفيذ حقيقية لكل المنصات في نطاقك، لكن تأكد أنها جميعًا تتبع نفس الواجهة.
  5. داخل فئة التجريد، أضف حقلًا مرجعيًا لنوع التنفيذ ، يفوض التجريد أغلب العمل إلى كائن التنفيذ الذي أشير إليه في ذلك الحقل.
  6. إن كان لديك عدة متغيرات من منطق عالي المستوى، فأنشئ تجريدًا منقحًا لكل متغير من خلال توسيع فئة التجريد الأساسية.
  7. يجب أن تمرر شيفرة العميل كائن تنفيذ إلى منشئ التجريد من أجل ربط أحدهما بالآخر، ثم بعدها يمكن للعميل أن ينسى التنفيذ ويعمل مع كائن التجريد فقط.

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

المزايا

يمكنك إنشاء فئات وتطبيقات تعمل على أي نظام تشغيل.

تعمل شيفرة العميل مع تجريدات عالية المستوى، ولا تتعرض إلى تفاصيل المنصة.

مبدأ المفتوح/المغلق. يمكنك إدخال تجريدات وتنفيذات جديدة بشكل مستقل عن بعضها البعض.

مبدأ المسؤولية الواحدة، يمكنك التركيز على المنطق عالي المستوى في التجريد وعلى تفاصيل المنصة في التنفيذ.

العيوب

قد تجعل الشيفرة أكثر تعقيدًا بتطبيق النمط على فئة عالية التماسك.

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

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

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

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

يمكنك جمع نمط الباني والجسر: تلعب فئة director دورًا في التجريد، بينما تعمل البانيات المختلفة كتنفيذات.

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

المستوى: ★ ★ ★

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

أمثلة الاستخدام: نمط الجسر مفيد جدًا عند التعامل مع البرامج متعددة المنصات أو دعم أنواع متعددة من خوادم قواعد البيانات أو مع عدة مزودي واجهة برمجة التطبيقات (API) لنوع بعينه (مثل المنصات السحابية أو الشبكات الاجتماعية أو غيرها).

يمكن ملاحظة نمط الجسر من خلال فرق واضح بين وحدة التحكم والمنصات المختلفة التي تعتمد عليها.

مثال: جسر بين الأجهزة ومتحكماتها عن بعد

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

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

الأجهزة

 devices/Device.java: واجهة مشتركة لكل الأجهزة
package refactoring_guru.bridge.example.devices;

public interface Device {
    boolean isEnabled();

    void enable();

    void disable();

    int getVolume();

    void setVolume(int percent);

    int getChannel();

    void setChannel(int channel);

    void printStatus();
}
 devices/Radio.java: راديو
package refactoring_guru.bridge.example.devices;

public class Radio implements Device {
    private boolean on = false;
    private int volume = 30;
    private int channel = 1;

    @Override
    public boolean isEnabled() {
        return on;
    }

    @Override
    public void enable() {
        on = true;
    }

    @Override
    public void disable() {
        on = false;
    }

    @Override
    public int getVolume() {
        return volume;
    }

    @Override
    public void setVolume(int volume) {
        if (volume > 100) {
            this.volume = 100;
        } else if (volume < 0) {
            this.volume = 0;
        } else {
            this.volume = volume;
        }
    }

    @Override
    public int getChannel() {
        return channel;
    }

    @Override
    public void setChannel(int channel) {
        this.channel = channel;
    }

    @Override
    public void printStatus() {
        System.out.println("------------------------------------");
        System.out.println("| أنا راديو.");
        System.out.println("| أنا " + (on ? "مفعّل" : "غير مفعّل"));
        System.out.println("| مستوى الصوت الحالي هو " + volume + "%");
        System.out.println("| القناة الحالية هي " + channel);
        System.out.println("------------------------------------\n");
    }
}
 devices/Tv.java: تلفاز
package refactoring_guru.bridge.example.devices;

public class Tv implements Device {
    private boolean on = false;
    private int volume = 30;
    private int channel = 1;

    @Override
    public boolean isEnabled() {
        return on;
    }

    @Override
    public void enable() {
        on = true;
    }

    @Override
    public void disable() {
        on = false;
    }

    @Override
    public int getVolume() {
        return volume;
    }

    @Override
    public void setVolume(int volume) {
        if (volume > 100) {
            this.volume = 100;
        } else if (volume < 0) {
            this.volume = 0;
        } else {
            this.volume = volume;
        }
    }

    @Override
    public int getChannel() {
        return channel;
    }

    @Override
    public void setChannel(int channel) {
        this.channel = channel;
    }

    @Override
    public void printStatus() {
        System.out.println("------------------------------------");
        System.out.println("| I'm TV set.");
        System.out.println("| I'm " + (on ? "enabled" : "disabled"));
        System.out.println("| مستوى الصوت الحالي هو " + volume + "%");
        System.out.println("| القناة الحالية هي " + channel);
        System.out.println("------------------------------------\n");
    }
}
 remotes/BasicRemote.java: متحكم عن بعد بسيط
package refactoring_guru.bridge.example.remotes;

import refactoring_guru.bridge.example.devices.Device;

public class BasicRemote implements Remote {
    protected Device device;

    public BasicRemote() {}

    public BasicRemote(Device device) {
        this.device = device;
    }

    @Override
    public void power() {
        System.out.println("المتحكم: زر تشغيل");
        if (device.isEnabled()) {
            device.disable();
        } else {
            device.enable();
        }
    }

    @Override
    public void volumeDown() {
        System.out.println("المتحكم: خفض الصوت");
        device.setVolume(device.getVolume() - 10);
    }

    @Override
    public void volumeUp() {
        System.out.println("المتحكم: رفع الصوت");
        device.setVolume(device.getVolume() + 10);
    }

    @Override
    public void channelDown() {
        System.out.println("المتحكم: قناة سابقة");
        device.setChannel(device.getChannel() - 1);
    }

    @Override
    public void channelUp() {
        System.out.println("المتحكم: قناة تالية");
        device.setChannel(device.getChannel() + 1);
    }
}
 remotes/AdvancedRemote.java: متحكم عن بعد متطور
package refactoring_guru.bridge.example.remotes;

import refactoring_guru.bridge.example.devices.Device;

public class AdvancedRemote extends BasicRemote {

    public AdvancedRemote(Device device) {
        super.device = device;
    }

    public void mute() {
        System.out.println("المتحكم: كتم الصوت");
        device.setVolume(0);
    }
}
 Demo.java: شيفرة العميل
package refactoring_guru.bridge.example;

import refactoring_guru.bridge.example.devices.Device;
import refactoring_guru.bridge.example.devices.Radio;
import refactoring_guru.bridge.example.devices.Tv;
import refactoring_guru.bridge.example.remotes.AdvancedRemote;
import refactoring_guru.bridge.example.remotes.BasicRemote;

public class Demo {
    public static void main(String[] args) {
        testDevice(new Tv());
        testDevice(new Radio());
    }

    public static void testDevice(Device device) {
        System.out.println("اختبارات مع المتحكم البسيط.");
        BasicRemote basicRemote = new BasicRemote(device);
        basicRemote.power();
        device.printStatus();

        System.out.println("اختبارات مع المتحكم المتطور.");
        AdvancedRemote advancedRemote = new AdvancedRemote(device);
        advancedRemote.power();
        advancedRemote.mute();
        device.printStatus();
    }
}
 OutputDemo.txt: نتائج التنفيذ
اختبارات مع المتحكم البسيط
المتحكم: زر التشغيل
------------------------------------
| أنا جهاز تلفاز
| أنا مفعّل
| مستوى الصوت الحالي هو %30
| القناة الحالية هي 1
------------------------------------

اختبارات مع المتحكم المتطور
المتحكم: زر التشغيل
المتحكم: كتم الصوت
------------------------------------
| أنا جهاز تلفاز
| أنا غير مفعّل
| مستوى الصوت الحالي هو %0
| القناة الحالية هي 1
------------------------------------

اختبارات مع المتحكم المتقدم
المتحكم: زر التشغيل
------------------------------------
| أنا راديو
| أنا مفعّل
| مستوى الصوت الحالي هو %30
| القناة الحالية هي 1
------------------------------------

اختبارات مع المتحكم المتقدم
المتحكم: زر التشغيل
المتحكم: كتم الصوت
------------------------------------
| أنا راديو
| أنا غير مفعّل
| مستوى الصوت الحالي هو %0
| القناة الحالية هي 1
------------------------------------

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

المستوى: ★ ★ ★

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

أمثلة الاستخدام: نمط الجسر مفيد جدًا عند التعامل مع البرامج متعددة المنصات أو دعم أنواع متعددة من خوادم قواعد البيانات أو مع عدة مزودي واجهة برمجة التطبيقات (API) لنوع بعينه (مثل المنصات السحابية أو الشبكات الاجتماعية أو غيرها).

يمكن ملاحظة نمط الجسر من خلال فرق واضح بين وحدة التحكم والمنصات المختلفة التي تعتمد عليها.

مثال: مثال تصوري

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

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

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

using System;

namespace RefactoringGuru.DesignPatterns.Bridge.Conceptual
{
    // يعرّف التجريد واجهة الجزء الخاص بالتحكم في الهرميات الفئوية، ويحافظ على مرجع
    // إلى كائن من هرمية التنفيذ ويفوض كل المهام الحقيقية إليه.
    class Abstraction
    {
        protected IImplementation _implementation;
        
        public Abstraction(IImplementation implementation)
        {
            this._implementation = implementation;
        }
        
        public virtual string Operation()
        {
            return "Abstract: Base operation with:\n" + 
                _implementation.OperationImplementation();
        }
    }

    // يمكنك توسيع التجريد دون تغيير فئات التنفيذ.
    class ExtendedAbstraction : Abstraction
    {
        public ExtendedAbstraction(IImplementation implementation) : base(implementation)
        {
        }
        
        public override string Operation()
        {
            return "ExtendedAbstraction: Extended operation with:\n" +
                base._implementation.OperationImplementation();
        }
    }

    // يعرّف التنفيذ واجهة كل فئات التنفيذ، ولا يجب أن تطابق واجهة التجريد، بل قد تكون
    // الواجهتان مختلفتان كليًا، وعادة ما توفر واجهة التنفيذ عمليات أولية، بينما تكون
    // واجهة التجريد مسؤولة عن عمليات عالية المستوى بناءً على تلك العمليات الأولية.
    public interface IImplementation
    {
        string OperationImplementation();
    }

    // يتوافق كل تنفيذ حقيقي مع منصة محددة ويستخدم واجهة التنفيذ من خلال
    // الخاص بالمنصة API
    class ConcreteImplementationA : IImplementation
    {
        public string OperationImplementation()
        {
            return "ConcreteImplementationA: The result in platform A.\n";
        }
    }

    class ConcreteImplementationB : IImplementation
    {
        public string OperationImplementation()
        {
            return "ConcreteImplementationA: The result in platform B.\n";
        }
    }

    class Client
    {
        // يجب أن تعتمد شيفرة العميل على طبقة التجريد فقط حيثما ارتبط كائن تجريد
        // بكائن تنفيذ ما، باستثناء مرحلة البدء. تستطيع شيفرة العميل بتلك الطريقة أن 
        // تدعم أي تجميعة تجريد-تنفيذ.
        public void ClientCode(Abstraction abstraction)
        {
            Console.Write(abstraction.Operation());
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            Client client = new Client();

            Abstraction abstraction;
            // يجب أن تكون شيفرة العميل قادرة على العمل مع أي تجميعة تجريد-تنفيذ
            // مسبقة التهيئة.
            abstraction = new Abstraction(new ConcreteImplementationA());
            client.ClientCode(abstraction);
            
            Console.WriteLine();
            
            abstraction = new ExtendedAbstraction(new ConcreteImplementationB());
            client.ClientCode(abstraction);
        }
    }
}

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

Abstract: Base operation with:
ConcreteImplementationA: The result in platform A.

ExtendedAbstraction: Extended operation with:
ConcreteImplementationA: The result in platform B.

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

المستوى: ★ ★ ★

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

أمثلة الاستخدام: نمط الجسر مفيد جدًا عند التعامل مع البرامج متعددة المنصات أو دعم أنواع متعددة من خوادم قواعد البيانات أو مع عدة مزودي واجهة برمجة التطبيقات (API) لنوع بعينه (مثل المنصات السحابية أو الشبكات الاجتماعية أو غيرها).

مثال: مثال تصوري

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

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

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

<?php

namespace RefactoringGuru\Bridge\Conceptual;

/** 
 * يعرّف التجريد واجهة الجزء الخاص بالتحكم في الهرميات الفئوية، ويحافظ على مرجع
 * إلى كائن من هرمية التنفيذ ويفوض كل المهام الحقيقية إليه.
 */
class Abstraction
{
    /**
     * @var Implementation
     */
    protected $implementation;

    public function __construct(Implementation $implementation)
    {
        $this->implementation = $implementation;
    }

    public function operation(): string
    {
        return "Abstraction: Base operation with:\n" .
            $this->implementation->operationImplementation();
    }
}

/**
 * يمكنك توسيع التجريد دون تغيير فئات التنفيذ.
 */
class ExtendedAbstraction extends Abstraction
{
    public function operation(): string
    {
        return "ExtendedAbstraction: Extended operation with:\n" .
            $this->implementation->operationImplementation();
    }
}

/**
 * يعرّف التنفيذ واجهة كل فئات التنفيذ، ولا يجب أن تطابق واجهة التجريد، بل قد تكون
 * الواجهتان مختلفتان كليًا، وعادة ما توفر واجهة التنفيذ عمليات أولية، بينما تكون
 * واجهة التجريد مسؤولة عن عمليات عالية المستوى بناءً على تلك العمليات الأولية.
 */
interface Implementation
{
    public function operationImplementation(): string;
}

/**
 * يتوافق كل تنفيذ حقيقي مع منصة محددة ويستخدم واجهة التنفيذ من خلال
 * الخاص بالمنصة API
 */
class ConcreteImplementationA implements Implementation
{
    public function operationImplementation(): string
    {
        return "ConcreteImplementationA: Here's the result on the platform A.\n";
    }
}

class ConcreteImplementationB implements Implementation
{
    public function operationImplementation(): string
    {
        return "ConcreteImplementationB: Here's the result on the platform B.\n";
    }
}

/**
 * يجب أن تعتمد شيفرة العميل على طبقة التجريد فقط حيثما ارتبط كائن تجريد
 * بكائن تنفيذ ما، باستثناء مرحلة البدء. تستطيع شيفرة العميل بتلك الطريقة أن 
 * تدعم أي تجميعة تجريد-تنفيذ.
 */
function clientCode(Abstraction $abstraction)
{
    // ...

    echo $abstraction->operation();

    // ...
}

/**
 * يجب أن تكون شيفرة العميل قادرة على العمل مع أي تجميعة تجريد-تنفيذ
 * مسبقة التهيئة.
 */
$implementation = new ConcreteImplementationA;
$abstraction = new Abstraction($implementation);
clientCode($abstraction);

echo "\n";

$implementation = new ConcreteImplementationB;
$abstraction = new ExtendedAbstraction($implementation);
clientCode($abstraction);

 Output.txt: المخرجات

Abstraction: Base operation with:
ConcreteImplementationA: Here's the result on the platform A.

ExtendedAbstraction: Extended operation with:
ConcreteImplementationB: Here's the result on the platform B.

مثال: حالة حقيقية

تتصرف هرمية الصفحة في هذا المثال كتجريد ويتصرف مخرج الهرمية (Hierarchy Renderer) كتنفيذ، وتستطيع كائنات فئة الصفحة أن تجمع صفحات وبل لنوع بعينه باستخدام عناصر أساسية تم توفيرها من قبل كائن الإخراج (Renderer Object) الملحق بتلك الصفحة. وبما أن هرمية الفئتين منفصلتان فيمكنك إضافة فئة مُخرج (Renderer) جديد دون تغيير أي من فئات الصفحة والعكس صحيح.

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

<?php

namespace RefactoringGuru\Bridge\RealWorld;

/**
 * التجريد.
 */
abstract class Page
{
    /**
     * @var Renderer
     */
    protected $renderer;

    /**
     * يُبدأ التجريد بواسطة أحد كائنات التنفيذ
     */
    public function __construct(Renderer $renderer)
    {
        $this->renderer = $renderer;
    }

    /**
     * يسمح نمط الجسر باستبدال كائن التنفيذ المُرفق بديناميكية.
     */
    public function changeRenderer(Renderer $renderer): void
    {
        $this->renderer = $renderer;
    }

    /**
     * مجردًا بما أن فئات التجريد الحقيقية هي فقط التي يمكنها توفيره view يبقى سلوك
     */
    abstract public function view(): string;
}

/**
 * يمثل هذا التجريد الحقيقي صفحة بسيطة.
 */
class SimplePage extends Page
{
    protected $title;
    protected $content;

    public function __construct(Renderer $renderer, string $title, string $content)
    {
        parent::__construct($renderer);
        $this->title = $title;
        $this->content = $content;
    }

    public function view(): string
    {
        return $this->renderer->renderParts([
            $this->renderer->renderHeader(),
            $this->renderer->renderTitle($this->title),
            $this->renderer->renderTextBlock($this->content),
            $this->renderer->renderFooter()
        ]);
    }
}

/**
 * يمثل هذا التجريد الحقيقي صفحة أكثر تعقيدًا.
 */
class ProductPage extends Page
{
    protected $product;

    public function __construct(Renderer $renderer, Product $product)
    {
        parent::__construct($renderer);
        $this->product = $product;
    }

    public function view(): string
    {
        return $this->renderer->renderParts([
            $this->renderer->renderHeader(),
            $this->renderer->renderTitle($this->product->getTitle()),
            $this->renderer->renderTextBlock($this->product->getDescription()),
            $this->renderer->renderImage($this->product->getImage()),
            $this->renderer->renderLink("/cart/add/" . $this->product->getId(), "Add to cart"),
            $this->renderer->renderFooter()
        ]);
    }
}

/**
 * ProductPage فئة مساعدة لفئة.
 */
class Product
{
    private $id, $title, $description, $image, $price;

    public function __construct(
        string $id,
        string $title,
        string $description,
        string $image,
        float $price
    ) {
        $this->id = $id;
        $this->title = $title;
        $this->description = $description;
        $this->image = $image;
        $this->price = $price;
    }

    public function getId(): string { return $this->id; }

    public function getTitle(): string { return $this->title; }

    public function getDescription(): string { return $this->description; }

    public function getImage(): string { return $this->image; }

    public function getPrice(): float { return $this->price; }
}


/**
 * platform ، under-the-hood ، real يصرح التنفيذ عن مجموعة أساليب
 * 
 * يسرد التنفيذ في هذه الحالة أساليب الإخراج التي يمكن استخدامها لتركيب أي صفحة ويب.
 * قد تستخدم التجريدات المختلفة أساليب مختلفة من التنفيذ.
 */
interface Renderer
{
    public function renderTitle(string $title): string;

    public function renderTextBlock(string $text): string;

    public function renderImage(string $url): string;

    public function renderLink(string $url, string $title): string;

    public function renderHeader(): string;

    public function renderFooter(): string;

    public function renderParts(array $parts): string;
}

/**
 * HTML هذا التنفيذ الحقيقي يخرج صفحة ويب كـ 
 */
class HTMLRenderer implements Renderer
{
    public function renderTitle(string $title): string
    {
        return "<h1>$title</h1>";
    }

    public function renderTextBlock(string $text): string
    {
        return "<div class='text'>$text</div>";
    }

    public function renderImage(string $url): string
    {
        return "<img src='$url'>";
    }

    public function renderLink(string $url, string $title): string
    {
        return "<a href='$url'>$title</a>";
    }

    public function renderHeader(): string
    {
        return "<html><body>";
    }

    public function renderFooter(): string
    {
        return "</body></html>";
    }

    public function renderParts(array $parts): string
    {
        return implode("\n", $parts);
    }
}

/**
 * JSON هذا التنفيذ الحقيقي يخرج صفحة ويب كنصوص
 */
class JsonRenderer implements Renderer
{
    public function renderTitle(string $title): string
    {
        return '"title": "' . $title . '"';
    }

    public function renderTextBlock(string $text): string
    {
        return '"text": "' . $text . '"';
    }

    public function renderImage(string $url): string
    {
        return '"img": "' . $url . '"';
    }

    public function renderLink(string $url, string $title): string
    {
        return '"link": {"href": "' . $title . '", "title": "' . $title . '""}';
    }

    public function renderHeader(): string
    {
        return '';
    }

    public function renderFooter(): string
    {
        return '';
    }

    public function renderParts(array $parts): string
    {
        return "{\n" . implode(",\n", array_filter($parts)) . "\n}";
    }
}

/**
 * تتعامل شيفرة العميل عادة مع كائنات التجريد فقط.
 */
function clientCode(Page $page)
{
    // ...

    echo $page->view();

    // ...
}

/**
 * يمكن تنفيذ شيفرة العميل مع أي تجميعة مسبقة التهيئة من التجريد والتنفيذ.
 */
$HTMLRenderer = new HTMLRenderer;
$JSONRenderer = new JsonRenderer;

$page = new SimplePage($HTMLRenderer, "Home", "Welcome to our website!");
echo "HTML view of a simple content page:\n";
clientCode($page);
echo "\n\n";

/**
 * يمكن أن يغير التجريدُ التنفيذَ المرتبط به أثناء التشغيل إن دعت الحاجة.
 */
$page->changeRenderer($JSONRenderer);
echo "JSON view of a simple content page, rendered with the same client code:\n";
clientCode($page);
echo "\n\n";


$product = new Product("123", "Star Wars, episode1",
    "A long time ago in a galaxy far, far away...",
    "/images/star-wars.jpeg", 39.95);

$page = new ProductPage($HTMLRenderer, $product);
echo "HTML view of a product page, same client code:\n";
clientCode($page);
echo "\n\n";

$page->changeRenderer($JSONRenderer);
echo "JSON view of a simple content page, with the same client code:\n";
clientCode($page);

 Output.txt: المخرجات

HTML عرض لصفحة محتوى بسيطة بأسلوب
<html><body>
<h1>Home</h1>
<div class='text'>Welcome to our website!</div>
</body></html>

أُخرجت بنفس شيفرة العميل ، JSON عرض لصفحة محتوى بسيطة بأسلوب
{
"title": "Home",
"text": "Welcome to our website!"
}

نفس شيفرة العميل ، HTML عرض لصفحة منتج بأسلوب
<html><body>
<h1>Star Wars, episode1</h1>
<div class='text'>A long time ago in a galaxy far, far away...</div>
<img src='/images/star-wars.jpeg'>
<a href='/cart/add/123'>Add to cart</a>
</body></html>

نفس شيفرة العميل ، JSON عرض لصفحة منتج بأسلوب
{
"title": "Star Wars, episode1",
"text": "A long time ago in a galaxy far, far away...",
"img": "/images/star-wars.jpeg",
"link": {"href": "Add to cart", "title": "Add to cart""}
}

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

المستوى: ★ ★ ★

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

أمثلة الاستخدام: نمط الجسر مفيد جدًا عند التعامل مع البرامج متعددة المنصات أو دعم أنواع متعددة من خوادم قواعد البيانات أو مع عدة مزودي واجهة برمجة التطبيقات (API) لنوع بعينه (مثل المنصات السحابية أو الشبكات الاجتماعية أو غيرها).

يمكن ملاحظة نمط الجسر من خلال فرق واضح بين وحدة التحكم والمنصات المختلفة التي تعتمد عليها.

مثال: مثال تصوري

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

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

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

from __future__ import annotations
from abc import ABC, abstractmethod


class Abstraction:
    """
    يعرّف التجريد واجهة الجزء الخاص بالتحكم في الهرميات الفئوية، ويحافظ على مرجع
    إلى كائن من هرمية التنفيذ ويفوض كل المهام الحقيقية إليه.
    """

    def __init__(self, implementation: Implementation) -> None:
        self.implementation = implementation

    def operation(self) -> str:
        return (f"Abstraction: Base operation with:\n"
                f"{self.implementation.operation_implementation()}")


class ExtendedAbstraction(Abstraction):
    """
    يمكنك توسيع التجريد دون تغيير فئات التنفيذ.
    """

    def operation(self) -> str:
        return (f"ExtendedAbstraction: Extended operation with:\n"
                f"{self.implementation.operation_implementation()}")


class Implementation(ABC):
    """
    يعرّف التنفيذ واجهة كل فئات التنفيذ، ولا يجب أن تطابق واجهة التجريد، بل قد تكون
    الواجهتان مختلفتان كليًا، وعادة ما توفر واجهة التنفيذ عمليات أولية، بينما تكون
    واجهة التجريد مسؤولة عن عمليات عالية المستوى بناءً على تلك العمليات الأولية.
    """

    @abstractmethod
    def operation_implementation(self) -> str:
        pass


"""
يتوافق كل تنفيذ حقيقي مع منصة محددة ويستخدم واجهة التنفيذ من خلال
الخاص بالمنصة API
"""


class ConcreteImplementationA(Implementation):
    def operation_implementation(self) -> str:
        return "ConcreteImplementationA: Here's the result on the platform A."


class ConcreteImplementationB(Implementation):
    def operation_implementation(self) -> str:
        return "ConcreteImplementationB: Here's the result on the platform B."


def client_code(abstraction: Abstraction) -> None:
    """
    يجب أن تعتمد شيفرة العميل على طبقة التجريد فقط حيثما ارتبط كائن تجريد
    بكائن تنفيذ ما، باستثناء مرحلة البدء. تستطيع شيفرة العميل بتلك الطريقة أن 
    تدعم أي تجميعة تجريد-تنفيذ.
    """

    # ...

    print(abstraction.operation(), end="")

    # ...


if __name__ == "__main__":
    """
    يجب أن تكون شيفرة العميل قادرة على العمل مع أي تجميعة تجريد-تنفيذ
    مسبقة التهيئة.
    """

    implementation = ConcreteImplementationA()
    abstraction = Abstraction(implementation)
    client_code(abstraction)

    print("\n")

    implementation = ConcreteImplementationB()
    abstraction = ExtendedAbstraction(implementation)
    client_code(abstraction)

 Output.txt: المخرجات

Abstraction: Base operation with:
ConcreteImplementationA: Here's the result on the platform A.

ExtendedAbstraction: Extended operation with:
ConcreteImplementationB: Here's the result on the platform B.