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

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
(2.3 محتوى)
(2.4 محتوى)
سطر 619: سطر 619:
 
namespace RefactoringGuru\Bridge\Conceptual;
 
namespace RefactoringGuru\Bridge\Conceptual;
  
/**
+
/**  
  *  
+
  * يعرّف التجريد واجهة الجزء الخاص بالتحكم في الهرميات الفئوية، ويحافظ على مرجع
  *  
+
  * إلى كائن من هرمية التطبيق ويفوض كل المهام الحقيقية إليه.
*
 
 
  */
 
  */
 
class Abstraction
 
class Abstraction
 
{
 
{
 
     /**
 
     /**
     * @var Implementation
+
     * @var تطبيق
 
     */
 
     */
 
     protected $implementation;
 
     protected $implementation;
سطر 644: سطر 643:
  
 
/**
 
/**
  * You can extend the Abstraction without changing the Implementation classes.
+
  * يمكنك توسيع التجريد دون تغيير فئات التطبيق.
 
  */
 
  */
 
class ExtendedAbstraction extends Abstraction
 
class ExtendedAbstraction extends Abstraction
سطر 656: سطر 655:
  
 
/**
 
/**
  * The Implementation defines the interface for all implementation classes. It
+
  * يعرّف التطبيق واجهة كل فئات التطبيق، ولا يجب أن تطابق واجهة التجريد، بل قد تكون
  * doesn't have to match the Abstraction's interface. In fact, the two
+
  * الواجهتان مختلفتان كليًا، وعادة ما توفر واجهة التطبيق عمليات أولية، بينما تكون
  * interfaces can be entirely different. Typically the Implementation interface
+
  * واجهة التجريد مسؤولة عن عمليات عالية المستوى بناءً على تلك العمليات الأولية.
* provides only primitive operations, while the Abstraction defines higher-
 
* level operations based on those primitives.
 
 
  */
 
  */
 
interface Implementation
 
interface Implementation
سطر 668: سطر 665:
  
 
/**
 
/**
  * Each Concrete Implementation corresponds to a specific platform and
+
  * يتوافق كل تطبيق حقيقي مع منصة محددة ويستخدم واجهة التطبيق من خلال
  * implements the Implementation interface using that platform's API.
+
  * الخاص بالمنصة API
 
  */
 
  */
 
class ConcreteImplementationA implements Implementation
 
class ConcreteImplementationA implements Implementation
سطر 688: سطر 685:
  
 
/**
 
/**
  * Except for the initialization phase, where an Abstraction object gets linked
+
  * يجب أن تعتمد شيفرة العميل على طبقة التجريد فقط حيثما ارتبط كائن تجريد
  * with a specific Implementation object, the client code should only depend on
+
  * بكائن تطبيق ما، باستثناء مرحلة البدء. تستطيع شيفرة العميل بتلك الطريقة أن
  * the Abstraction class. This way the client code can support any abstraction-
+
  * تدعم أي تجميعة تجريد-تطبيق.
* implementation combination.
 
 
  */
 
  */
 
function clientCode(Abstraction $abstraction)
 
function clientCode(Abstraction $abstraction)
سطر 703: سطر 699:
  
 
/**
 
/**
  * The client code should be able to work with any pre-configured abstraction-
+
  * يجب أن تكون شيفرة العميل قادرة على العمل مع أي تجميعة تجريد-تطبيق
  * implementation combination.
+
  * مسبقة التهيئة.
 
  */
 
  */
 
$implementation = new ConcreteImplementationA;
 
$implementation = new ConcreteImplementationA;
سطر 716: سطر 712:
 
clientCode($abstraction);
 
clientCode($abstraction);
  
 +
</syntaxhighlight>
 +
 +
==== Output.txt: المخرجات ====
 +
<syntaxhighlight>
 +
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.
 +
</syntaxhighlight>
 +
 +
=== مثال: حالة حقيقية ===
 +
تتصرف هرمية الصفحة في هذا المثال كتجريد ويتصرف مخرج الهرمية (Hierarchy Renderer) كتطبيق، وتستطيع كائنات فئة الصفحة أن تجمع صفحات وبل لنوع بعينه باستخدام عناصر أساسية تم توفيرها من قبل كائن الإخراج (Renderer Object) الملحق بتلك الصفحة. وبما أن هرمية الفئتين منفصلتان فيمكنك إضافة فئة مُخرج (Renderer) جديد دون تغيير أي من فئات الصفحة والعكس صحيح.
 +
 +
==== index.php: مثال واقعي ====
 +
<syntaxhighlight lang="php">
 +
<?php
 +
 +
namespace RefactoringGuru\Bridge\RealWorld;
 +
 +
/**
 +
* التجريد.
 +
*/
 +
abstract class Page
 +
{
 +
    /**
 +
    * @var
 +
    */
 +
    protected $renderer;
 +
 +
    /**
 +
    * The Abstraction is usually initialized with one of the Implementation
 +
    * objects.
 +
    */
 +
    public function __construct(Renderer $renderer)
 +
    {
 +
        $this->renderer = $renderer;
 +
    }
 +
 +
    /**
 +
    * The Bridge pattern allows replacing the attached Implementation object
 +
    * dynamically.
 +
    */
 +
    public function changeRenderer(Renderer $renderer): void
 +
    {
 +
        $this->renderer = $renderer;
 +
    }
 +
 +
    /**
 +
    * The "view" behavior stays abstract since it can only be provided by
 +
    * Concrete Abstraction classes.
 +
    */
 +
    abstract public function view(): string;
 +
}
 +
 +
/**
 +
* This Concrete Abstraction represents a simple page.
 +
*/
 +
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()
 +
        ]);
 +
    }
 +
}
 +
 +
/**
 +
* This Concrete Abstraction represents a more complex page.
 +
*/
 +
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()
 +
        ]);
 +
    }
 +
}
 +
 +
/**
 +
* A helper class for the ProductPage class.
 +
*/
 +
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; }
 +
}
 +
 +
 +
/**
 +
* The Implementation declares a set of "real", "under-the-hood", "platform"
 +
* methods.
 +
*
 +
* In this case, the Implementation lists rendering methods that can be used to
 +
* compose any web page. Different Abstractions may use different methods of the
 +
* 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;
 +
}
 +
 +
/**
 +
* This Concrete Implementation renders a web page as 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);
 +
    }
 +
}
 +
 +
/**
 +
* This Concrete Implementation renders a web page as JSON strings.
 +
*/
 +
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}";
 +
    }
 +
}
 +
 +
/**
 +
* The client code usually deals only with the Abstraction objects.
 +
*/
 +
function clientCode(Page $page)
 +
{
 +
    // ...
 +
 +
    echo $page->view();
 +
 +
    // ...
 +
}
 +
 +
/**
 +
* The client code can be executed with any pre-configured combination of the
 +
* Abstraction+Implementation.
 +
*/
 +
$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";
 +
 +
/**
 +
* The Abstraction can change the linked Implementation at runtime if needed.
 +
*/
 +
$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);
 
</syntaxhighlight>
 
</syntaxhighlight>

مراجعة 14:27، 10 فبراير 2019

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

المشكلة

لنقل أن لديك فئة هندسية اسمها 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) المختلفة تبادلية طالما تتبع واجهة مشتركة، مما يجعل الواجهة الرسومية تعمل في ويندوز ولينكس معًا.

البُنية

ضع الصورة.

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

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

المزايا

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

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

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

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

العيوب

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

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

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

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

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

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

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

المستوى: ★ ★ ★

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

أمثلة الاستخدام: نمط الجسر مفيد جدًا عند التعامل مع البرامج متعددة المنصات أو دعم أنواع متعددة من خوادم قواعد البيانات أو مع عدة مزودي واجهة برمجة التطبيقات (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 تطبيق
     */
    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 
     */
    protected $renderer;

    /**
     * The Abstraction is usually initialized with one of the Implementation
     * objects.
     */
    public function __construct(Renderer $renderer)
    {
        $this->renderer = $renderer;
    }

    /**
     * The Bridge pattern allows replacing the attached Implementation object
     * dynamically.
     */
    public function changeRenderer(Renderer $renderer): void
    {
        $this->renderer = $renderer;
    }

    /**
     * The "view" behavior stays abstract since it can only be provided by
     * Concrete Abstraction classes.
     */
    abstract public function view(): string;
}

/**
 * This Concrete Abstraction represents a simple page.
 */
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()
        ]);
    }
}

/**
 * This Concrete Abstraction represents a more complex page.
 */
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()
        ]);
    }
}

/**
 * A helper class for the ProductPage class.
 */
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; }
}


/**
 * The Implementation declares a set of "real", "under-the-hood", "platform"
 * methods.
 *
 * In this case, the Implementation lists rendering methods that can be used to
 * compose any web page. Different Abstractions may use different methods of the
 * 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;
}

/**
 * This Concrete Implementation renders a web page as 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);
    }
}

/**
 * This Concrete Implementation renders a web page as JSON strings.
 */
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}";
    }
}

/**
 * The client code usually deals only with the Abstraction objects.
 */
function clientCode(Page $page)
{
    // ...

    echo $page->view();

    // ...
}

/**
 * The client code can be executed with any pre-configured combination of the
 * Abstraction+Implementation.
 */
$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";

/**
 * The Abstraction can change the linked Implementation at runtime if needed.
 */
$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);