نمط الجسر
نمط الجسر هو نمط تصميم هيكلي يسمح لك بتقسيم فئة كبيرة أو مجموعة فئات مرتبطة ببعضها إلى تشكيلين هرميين منفصلين -نظري وتطبيقي-، ومن ثم يمكن تطويرهما بشكل مستقل عن بعضهما.
المشكلة
لنقل أن لديك فئة هندسية اسمها 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) المختلفة تبادلية طالما تتبع واجهة مشتركة، مما يجعل الواجهة الرسومية تعمل في ويندوز ولينكس معًا.
البُنية
ضع الصورة.
- يوفر التجريد (Abstraction) منطق تحكم عالي المستوى، إذ يعتمد على كائن التطبيق (implementation object) للقيام بالمهام الفعلية منخفضة المستوى.
- يصرح التطبيق (Implementation) عن الواجهة الشائعة لكل التطبيقات الحقيقية (Concrete Implementations)، ويتواصل أي تجريد مع كائن تطبيق من خلال الأساليب التي صُرِّح بها هنا فقط. وقد يسرد التجريد نفس الأساليب أيضًا لكنه عادة يصرح عن بعض السلوكيات المعقدة التي تعتمد على صور مختلفة من العمليات الأولية التي يصرح عنها التطبيق (implementation).
- تحتوي التطبيقات الحقيقية (Concrete Implementations) على شيفرات خاصة بالمنصات الموجه إليها البرنامج (Platform-specific).
- التجريدات المنقحة (refined abstractions) توفر صورًا مختلفة من منطق التحكم (control logic)، وتعمل مع التطبيقات (implementations) المختلفة من خلال واجهة تطبيق عامة (general implementation interface).
- عادة لا يهتم العميل إلا بالعمل مع التجريد، لكن وظيفته على أي حال هي ربط كائن التجريد مع أحد كائنات التطبيق.
مثال توضيحي
يشرح هذا المثال كيف يُستخدم نمط الجسر لتقسيم الشيفرة الأحادية لبرنامج يدير بعض الأجهزة الإلكترونية وأجهزة التحكم فيها عن بعد (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) فيها متغيرات عديدة لبعض الوظائف (كأن تعمل الفئة مع عدة خوادم لقواعد البيانات).
كلما كبر حجم الفئة صعُبَ فهم كيفية عملها، وطالت عملية إحداث تغيير فيها، وقد تتطلب التغييرات التي تُجرى على أحد صور الوظائف إحداث تغييرات في الفئة كلها، الأمر الذي يتسبب عادة في حدوث أخطاء أو ترك بعض الآثار الجانبية الحرجة دون معالجة.
يسمح لك نمط الجسر بتقسيم الفئة الأحادية إلى عدة هرميات فئوية، ثم بعد ذلك يمكنك تغيير الفئات في كل هرمية بشكل مستقل عن الفئات التي في الهرميات الأخرى، ويبسط هذا المنظور عملية صيانة الشيفرة ويقلل خطورة تعطيل شيفرة حالية.
استخدام نمط الجسر حين تحتاج إلى زيادة فئة في عدة أبعاد متعامدة (مستقلة)