«Design Patterns/bridge»: الفرق بين المراجعتين

من موسوعة حسوب
اذهب إلى: تصفح، ابحث
(التجريد والتطبيق(Abstraction and Implementation))
ط
 
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:نمط الجسر}}</noinclude>
+
<noinclude>{{DISPLAYTITLE:نمط الجسر Bridge}}</noinclude>
 
نمط الجسر هو نمط تصميم هيكلي يسمح لك بتقسيم فئة كبيرة أو مجموعة فئات مرتبطة ببعضها إلى تشكيلين هرميين منفصلين -نظري وتطبيقي-، ومن ثم يمكن تطويرهما بشكل مستقل عن بعضهما.
 
نمط الجسر هو نمط تصميم هيكلي يسمح لك بتقسيم فئة كبيرة أو مجموعة فئات مرتبطة ببعضها إلى تشكيلين هرميين منفصلين -نظري وتطبيقي-، ومن ثم يمكن تطويرهما بشكل مستقل عن بعضهما.
  

المراجعة الحالية بتاريخ 13:45، 4 مارس 2020

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

محتويات

المشكلة

(ش.1) يزيد عدد تجميعات الفئات مع إضافة المزيد من الأشكال الهندسية.

لنقل أن لديك فئة هندسية (Geometric) اسمها shape، ولتلك الفئة زوج من الفئات الفرعية هما circle وsquare، وتريد توسيع هرمية تلك الفئة لتضيف الألوان على فئات تلك الأشكال الهندسية، فسيكون الحل البديهي هنا أن تنشئ فئتين فرعيتين لفئة shape هما Red و Blue مثلًا. لكن بما أن لديك فئتين فرعيتين من البداية، فستحتاج إلى إنشاء أربع تجميعات (Combinations) لتحتوي الاحتمالات الممكنة للأشكال وألوانها التي قد تأخذها، مثل BlueCircle و RedSquare. انظر (ش.1)

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

الحل

(ش.2)

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

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

التجريد والتطبيق (Abstraction and Implementation)

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

والتجريد (والذي هو الواجهة -interface- أيضًا) هو طبقة تحكّم عالية المستوى لبعض الكيانات (entity)، لا يفترض بها تطبيق أي عمل حقيقي من تلقاء نفسها، بل يجب أن تفوض الأعمال إلى طبقة التطبيق (والتي يطلق عليها المنصة أيضًا -platform-). لاحظ أننا لا نتكلم عن الواجهات (interfaces) أو الفئات المجردة (abstract classes) في لغتك البرمجية، فتلك أمور مختلفة عما نتحدث عنه هنا، ذلك أنه عند الحديث عن التطبيقات الحقيقية، فإن التجريد يمكن تمثيله بواجهة مستخدم رسومية GUIـ أما التطبيق فقد يكون أي شيفرة نظام تشغيل (API) تستدعيها طبقة الواجهة الرسومية كاستجابة لتفاعلات المستخدم.
(ش.3) يصعب إجراء أي تغيير ولو بسيط في الشيفرة الأحادية (Monolithic) لحاجتك إلى فهم التطبيق ككل، على عكس تقسيم التطبيق إلى وحدات أصغر ثم تطبيق التغيير على الأجزاء التي تحتاج تغييرًا فقط.
وبشكل عام يمكنك توسيع مثل ذلك التطبيق في اتجاهين منفصلين:
  • توفير عدة واجهات رسومية (كواجهات مخصصة للعملاء العاديين أو للمدراء -Admins-).
  • دعم عدة واجهات لبرمجة تطبيقات (APIs)، كأن يكون قادرًا على تشغيل التطبيق في ويندوز ولينكس وماك.

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

ضع الصورة.

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

(ش.4) إحدى الطرق لهيكلة تطبيق متعدد المنصات.

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

  • التجريد (Abstraction): الواجهة الرسومية للتطبيق (App).
  • التطبيق (Implementation): واجهات برمجة التطبيقات (APIs) الخاصة بنظام التشغيل.

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

البُنية

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

مثال توضيحي

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

يشرح هذا المثال كيف يُستخدم نمط الجسر لتقسيم الشيفرة الأحادية لبرنامج يدير بعض الأجهزة الإلكترونية والمتحكمات فيها عن بعد (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)


// عن أساليب شائعة بين كل فئات التطبيق الحقيقية، وليس Implementation تصرح واجهة التطبيق
// ضروريًا أن تطابق واجهة التجريد، بل قد تختلف واجهتين تمامًا.
//  وتوفر واجهة التطبيق عمليات أولية فقط، بينما يحدد التجريد العمليات ذات
// المستوى الأعلى بناءً على تلك الأولية.
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 يصرح التطبيق عن مجموعة أساليب
 * 
 * يسرد التطبيق في هذه الحالة أساليب الإخراج التي يمكن استخدامها لتركيب أي صفحة ويب.
 * (implementation) قد تستخدم التجريدات المختلفة أساليب مختلفة من التطبيق .
 */
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.

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

المستوى: ★ ★ ★

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

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

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

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

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

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

# يحدد التجريد واجهة الجزء الخاص بالتحكم في الهرميات الفئوية، ويحافظ على
# مرجع إلى كائن من هرمية التطبيق ويفوض كل المهام الحقيقية إليه.
class Abstraction
  # @param [Implementation] implementation
  def initialize(implementation)
    @implementation = implementation
  end

  # @return [String]
  def operation
    "Abstraction: Base operation with:\n"\
    "#{@implementation.operation_implementation}"
  end
end

# يمكنك توسيع التجريد دون تغيير فئات التطبيق.
class ExtendedAbstraction < Abstraction
  # @return [String]
  def operation
    "ExtendedAbstraction: Extended operation with:\n"\
    "#{@implementation.operation_implementation}"
  end
end

# يحدد التطبيق واجهة كل فئات التطبيق، ولا يجب أن تطابق واجهة التجريد، 
# بل قد تكون الواجهتان مختلفتان كليًا، وعادة ما توفر واجهة التطبيق عمليات 
# أولية، بينما تكون واجهة التجريد مسؤولة عن عمليات عالية المستوى بناءً 
# على تلك العمليات الأولية.
class Implementation
  # @abstract
  #
  # @return [String]
  def operation_implementation
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end

# يتوافق كل تطبيق حقيقي مع منصة محددة ويستخدم واجهة التطبيق من خلال
# الخاص بالمنصة API
class ConcreteImplementationA < Implementation
  # @return [String]
  def operation_implementation
    'ConcreteImplementationA: Here\'s the result on the platform A.'
  end
end

class ConcreteImplementationB < Implementation
  # @return [String]
  def operation_implementation
    'ConcreteImplementationB: Here\'s the result on the platform B.'
  end
end

# يجب أن تعتمد شيفرة العميل على طبقة التجريد فقط حيثما ارتبط كائن تجريد
# بكائن استخدام ما، باستثناء مرحلة التهيئة. تستطيع شيفرة العميل بتلك 
# الطريقة أن تدعم أي تجميعة تجريد-تطبيق.
def client_code(abstraction)
  # ...

  print abstraction.operation

  # ...
end

# يجب أن تكون شيفرة العميل قادرة على العمل مع أي تجميعة تجريد-تطبيق
# مسبقة التهيئة.

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

puts "\n\n"

implementation = ConcreteImplementationB.new
abstraction = ExtendedAbstraction.new(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.

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

المستوى: ★ ★ ★

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

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

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

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

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

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

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

import XCTest

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

    fileprivate var implementation: Implementation

    init(_ implementation: Implementation) {
        self.implementation = implementation
    }

    func operation() -> String {
        let operation = implementation.operationImplementation()
        return "Abstraction: Base operation with:\n" + operation
    }
}

/// يمكنك توسيع التجريد دون تغيير فئات التطبيق.
class ExtendedAbstraction: Abstraction {

    override func operation() -> String {
        let operation = implementation.operationImplementation()
        return "ExtendedAbstraction: Extended operation with:\n" + operation
    }
}

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

    func operationImplementation() -> String
}

/// يتوافق كل تطبيق حقيقي مع منصة محددة ويستخدم واجهة التطبيق من خلال
/// الخاص بالمنصة API
class ConcreteImplementationA: Implementation {

    func operationImplementation() -> String {
        return "ConcreteImplementationA: Here's the result on the platform A.\n"
    }
}

class ConcreteImplementationB: Implementation {

    func operationImplementation() -> String {
        return "ConcreteImplementationB: Here's the result on the platform B\n"
    }
}

/// يجب أن تعتمد شيفرة العميل على طبقة التجريد فقط حيثما ارتبط كائن تجريد
/// بكائن استخدام ما، باستثناء مرحلة التهيئة. تستطيع شيفرة العميل بتلك 
/// الطريقة أن تدعم أي تجميعة تجريد-تطبيق.
class Client {
    // ...
    static func someClientCode(abstraction: Abstraction) {
        print(abstraction.operation())
    }
    // ...
}

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

    func testBridgeConceptual() {
        // يجب أن تكون شيفرة العميل قادرة على العمل مع أي تجميعة تجريد-تطبيق
        // مسبقة التهيئة.
        let implementation = ConcreteImplementationA()
        Client.someClientCode(abstraction: Abstraction(implementation))

        let concreteImplementation = ConcreteImplementationB()
        Client.someClientCode(abstraction: ExtendedAbstraction(concreteImplementation))
    }
}

 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

مثال واقعي

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

import XCTest

private class BridgeRealWorld: XCTestCase {

    func testBridgeRealWorld() {

        print("Client: Pushing Photo View Controller...")
        push(PhotoViewController())

        print()

        print("Client: Pushing Feed View Controller...")
        push(FeedViewController())
    }

    func push(_ container: SharingSupportable) {

        let instagram = InstagramSharingService()
        let facebook = FaceBookSharingService()

        container.accept(service: instagram)
        container.update(content: foodModel)

        container.accept(service: facebook)
        container.update(content: foodModel)
    }

    var foodModel: Content {
        return FoodDomainModel(title: "This food is so various and delicious!",
                               images: [UIImage(), UIImage()],
                               calories: 47)
    }
}

private protocol SharingSupportable {

    /// التجريد
    func accept(service: SharingService)

    func update(content: Content)
}

class BaseViewController: UIViewController, SharingSupportable {

    fileprivate var shareService: SharingService?

    func update(content: Content) {
        /// ...تحديث الواجهة الرسومية وإظهار المحتوى...
        /// ...
        /// ... ثم سيختار المستخدمُ المحتوى ويبدأ حدثًا.
        print("\(description): User selected a \(content) to share")
        /// ...
        shareService?.share(content: content)
    }

    func accept(service: SharingService) {
        shareService = service
    }
}

class PhotoViewController: BaseViewController {

    /// واجهة رسومية ومزايا مخصصة

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

class FeedViewController: BaseViewController {

    /// واجهة رسومية ومزايا مخصصة

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

protocol SharingService {

    /// (Implementation) التطبيق
    func share(content: Content)
}

class FaceBookSharingService: SharingService {

    func share(content: Content) {

        /// لنشر محتوى Facebook API استخدم واجهة تطبييق فيس بوك
        print("Service: \(content) was posted to the Facebook")
    }
}

class InstagramSharingService: SharingService {

    func share(content: Content) {

        /// لنشر محتوى Instagram API استخدم واجهة تطبييق إنستجرام
        print("Service: \(content) was posted to the Instagram", terminator: "\n\n")
    }
}

protocol Content: CustomStringConvertible {

    var title: String { get }
    var images: [UIImage] { get }
}

struct FoodDomainModel: Content {

    var title: String
    var images: [UIImage]
    var calories: Int

    var description: String {
        return "Food Model"
    }
}

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

Client: Pushing Photo View Controller...
PhotoViewController: User selected a Food Model to share
Service: Food Model was posted to the Instagram

PhotoViewController: User selected a Food Model to share
Service: Food Model was posted to the Facebook

Client: Pushing Feed View Controller...
FeedViewController: User selected a Food Model to share
Service: Food Model was posted to the Instagram

FeedViewController: User selected a Food Model to share
Service: Food Model was posted to the Facebook

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

المستوى: ★ ★ ★

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

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

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

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

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

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

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

    constructor(implementation: Implementation) {
        this.implementation = implementation;
    }

    public operation(): string {
        const result = this.implementation.operationImplementation();
        return `Abstraction: Base operation with:\n${result}`;
    }
}

/**
 * يمكنك توسيع التجريد دون تغيير فئات التطبيق.
 */
class ExtendedAbstraction extends Abstraction {
    public operation(): string {
        const result = this.implementation.operationImplementation();
        return `ExtendedAbstraction: Extended operation with:\n${result}`;
    }
}

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

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

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

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

    console.log(abstraction.operation());

    // ..
}

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

console.log('');

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.

انظر أيضًا

مصادر