---
title: "المهايئ (Adapter)"
type: "design-pattern"
slug: "adapter"
url: "http://localhost:3000/ar/design-patterns/adapter.md"
category: "الأنماط الهيكلية"
description: "المهايئ (Adapter) هو نمط تصميم هيكلي يسمح للكائنات ذات الواجهات غير المتوافقة بالتعاون معاً."
languages: ["java"]
---
# المهايئ (Adapter)

> المهايئ هو نمط تصميم هيكلي يسمح للكائنات ذات الواجهات غير المتوافقة بالتعاون معاً.

## الغرض

**المهايئ** هو نمط تصميم هيكلي يسمح للكائنات ذات الواجهات غير المتوافقة بالتعاون معاً.

## المشكلة

تخيل أنك تقوم بإنشاء تطبيق لمراقبة سوق الأسهم. يقوم التطبيق بتنزيل بيانات الأسهم من مصادر متعددة بتنسيق XML ثم يعرض مخططات ورسومات بيانية جذابة للمستخدم.

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

لا يمكنك استخدام مكتبة التحليلات “كما هي” لأنها تتوقع البيانات بتنسيق غير متوافق مع تطبيقك.

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

## الحل

يمكنك إنشاء _مهايئ_. هذا كائن خاص يقوم بتحويل واجهة كائن ما بحيث يمكن لكائن آخر فهمها.

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

لا يمكن للمهايئات تحويل البيانات إلى تنسيقات مختلفة فحسب، بل يمكنها أيضاً مساعدة الكائنات ذات الواجهات المختلفة على التعاون. وإليك كيف يعمل:

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

في بعض الأحيان يكون من الممكن إنشاء مهايئ ثنائي الاتجاه يمكنه تحويل الاستدعاءات في كلا الاتجاهين.

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

## الهيكل

#### مهايئ الكائنات (Object adapter)

يعتمد هذا التنفيذ على مبدأ تركيب الكائنات (object composition): يقوم المهايئ بتنفيذ واجهة كائن ما ويلف الآخر. ويمكن تنفيذه في جميع لغات البرمجة الشائعة.

1. **العميل (Client)** هو فئة تحتوي على منطق العمل الحالي للبرنامج.
2. **واجهة العميل (Client Interface)** تصف بروتوكولاً يجب على الفئات الأخرى اتباعه لتكون قادرة على التعاون مع كود العميل.
3. **الخدمة (Service)** هي فئة مفيدة (عادةً ما تكون تابعة لجهة خارجية أو كود قديم). لا يمكن للعميل استخدام هذه الفئة مباشرة لأنها تحتوي على واجهة غير متوافقة.
4. **المهايئ (Adapter)** هو فئة قادرة على العمل مع كل من العميل الخدمة: فهو ينفذ واجهة العميل، ويلف كائن الخدمة. يتلقى المهايئ استدعاءات من العميل عبر واجهة العميل ويترجمها إلى استدعاءات لكائن الخدمة الملفوف بتنسيق يمكنه فهمه.
5. لا يرتبط كود العميل بفئة المهايئ الملموسة طالما أنه يعمل مع المهايئ عبر واجهة العميل. بفضل هذا، يمكنك تقديم أنواع جديدة من المهايئات في البرنامج دون كسر كود العميل الحالي. يمكن أن يكون هذا مفيداً عند تغيير واجهة فئة الخدمة أو استبدالها: يمكنك فقط إنشاء فئة مهايئ جديدة دون تغيير كود العميل.

#### مهايئ الفئات (Class adapter)

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

1. لا يحتاج **مهايئ الفئة (Class Adapter)** إلى تغليف أي كائنات لأنه يرث السلوكيات من كل من العميل والخدمة. يحدث التكيف داخل الدوال المعاد تعريفها (overridden methods). يمكن استخدام المهايئ الناتج بدلاً من فئة العميل الحالية.

## الكود الوهمي (Pseudocode)

يعتمد هذا المثال لنمط **المهايئ (Adapter)** على الصراع الكلاسيكي بين الأوتاد المربعة والثقوب المستديرة.

تكييف الأوتاد المربعة مع الثقوب المستديرة.

يتظاهر المهايئ بأنه وتد مستدير، بنصف قطر يساوي نصف قطر قطر المربع (بمعنى آخر، نصف قطر أصغر دائرة يمكن أن تستوعب الوتد المربع).

// لنفترض أن لديك فئتين بواجهات متوافقة:
// RoundHole و RoundPeg.
class RoundHole is
    constructor RoundHole(radius) { ... }

    method getRadius() is
        // إرجاع نصف قطر الثقب.

    method fits(peg: RoundPeg) is
        return this.getRadius() >= peg.getRadius()

class RoundPeg is
    constructor RoundPeg(radius) { ... }

    method getRadius() is
        // إرجاع نصف قطر الوتد.

// ولكن هناك فئة غير متوافقة: SquarePeg.
class SquarePeg is
    constructor SquarePeg(width) { ... }

    method getWidth() is
        // إرجاع عرض الوتد المربع.

// تتيح لك فئة المهايئ ملاءمة الأوتاد المربعة في الثقوب المستديرة.
// وهي توسع فئة RoundPeg لتمكين كائنات المهايئ من العمل كأوتاد مستديرة.
class SquarePegAdapter extends RoundPeg is
    // في الواقع، يحتوي المهايئ على نسخة من فئة SquarePeg.
    private field peg: SquarePeg

    constructor SquarePegAdapter(peg: SquarePeg) is
        this.peg = peg

    method getRadius() is
        // يتظاهر المهايئ بأنه وتد مستدير بنصف قطر يمكنه
        // استيعاب الوتد المربع الذي يلفه المهايئ بالفعل.
        return peg.getWidth() * Math.sqrt(2) / 2

// في مكان ما في كود العميل.
hole = new RoundHole(5)
rpeg = new RoundPeg(5)
hole.fits(rpeg) // true

small_sqpeg = new SquarePeg(5)
large_sqpeg = new SquarePeg(10)
hole.fits(small_sqpeg) // لن يتم تجميع هذا (أنواع غير متوافقة)

small_sqpeg_adapter = new SquarePegAdapter(small_sqpeg)
large_sqpeg_adapter = new SquarePegAdapter(large_sqpeg)
hole.fits(small_sqpeg_adapter) // true
hole.fits(large_sqpeg_adapter) // false

## التطبيق العملي

استخدم فئة المهايئ (Adapter) عندما تريد استخدام فئة موجودة بالفعل، ولكن واجهتها ليست متوافقة مع بقية الكود الخاص بك.

 يتيح لك نمط المهايئ إنشاء فئة وسيطة تعمل كمترجم بين كودك وفئة قديمة أو فئة تابعة لجهة خارجية (3rd-party) أو أي فئة أخرى ذات واجهة غريبة.

 استخدم هذا النمط عندما تريد إعادة استخدام العديد من الفئات الفرعية (subclasses) الحالية التي تفتقر إلى بعض الوظائف المشتركة التي لا يمكن إضافتها إلى الفئة الأساسية (superclass).

 يمكنك توسيع كل فئة فرعية ووضع الوظائف المفقودة في فئات فرعية جديدة. ومع ذلك، ستحتاج إلى تكرار الكود عبر جميع هذه الفئات الجديدة، وهو أمر [سيء للغاية](/smells/duplicate-code).

الحل الأكثر أناقة هو وضع الوظيفة المفقودة في فئة مهايئ. بعد ذلك، يمكنك تغليف الكائنات التي تفتقر إلى الميزات داخل المهايئ، مما يمنحها الميزات المطلوبة ديناميكياً. لكي يعمل هذا، يجب أن تمتلك الفئات المستهدفة واجهة مشتركة، ويجب أن يتبع حقل المهايئ تلك الواجهة. يبدو هذا النهج مشابهاً جداً لنمط [المزخرف (Decorator)](/design-patterns/decorator) pattern.

## كيفية التنفيذ

1. تأكد من أن لديك فئتين على الأقل بواجهات غير متوافقة:

  * فئة _خدمة_ مفيدة لا يمكنك تغييرها (غالباً ما تكون تابعة لجهة خارجية، أو كود قديم، أو تحتوي على الكثير من الاعتماديات الحالية).
  * فئة _عميل_ واحدة أو أكثر ستستفيد من استخدام فئة الخدمة.
2. أعلن عن واجهة العميل ووصف كيف يتواصل العملاء مع الخدمة.
3. أنشئ فئة المهايئ واجعلها تتبع واجهة العميل. اترك جميع الدوال فارغة في الوقت الحالي.
4. أضف حقلاً إلى فئة المهايئ لتخزين مرجع لكائن الخدمة. الممارسة الشائعة هي تهيئة هذا الحقل عبر المنشئ (constructor)، ولكن في بعض الأحيان يكون من الأنسب تمريره إلى المهايئ عند استدعاء دواله.
5. واحدة تلو الأخرى، قم بتنفيذ جميع دوال واجهة العميل في فئة المهايئ. يجب على المهايئ تفويض معظم العمل الفعلي لكائن الخدمة، مع معالجة الواجهة أو تحويل تنسيق البيانات فقط.
6. يجب على العملاء استخدام المهايئ عبر واجهة العميل. سيتيح لك ذلك تغيير المهايئات أو توسيعها دون التأثير على كود العميل.

## الإيجابيات

* _مبدأ المسؤولية الفردية_. يمكنك فصل واجهة أو كود تحويل البيانات عن منطق العمل الأساسي للبرنامج.
* _مبدأ المفتوح/المغلق_. يمكنك تقديم أنواع جديدة من المهايئات في البرنامج دون كسر كود العميل الحالي، طالما أنها تعمل مع المهايئات من خلال واجهة العميل.

## السلبيات

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

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

* يتم تصميم [الجسر (Bridge)](/design-patterns/bridge) عادةً بشكل مسبق، مما يتيح لك تطوير أجزاء من التطبيق بشكل مستقل عن بعضها البعض. من ناحية أخرى، يُستخدم [المهايئ (Adapter)](/design-patterns/adapter) بشكل شائع مع تطبيق موجود لجعل بعض الفئات غير المتوافقة تعمل معاً بشكل جيد.
* يوفر [المهايئ (Adapter)](/design-patterns/adapter) واجهة مختلفة تماماً للوصول إلى كائن موجود. من ناحية أخرى، مع نمط [المزخرف (Decorator)](/design-patterns/decorator) تظل الواجهة كما هي أو يتم توسيعها. بالإضافة إلى ذلك، يدعم _المزخرف_ التركيب العودي، وهو أمر غير ممكن عند استخدام _المهايئ_.
* باستخدام [المهايئ (Adapter)](/design-patterns/adapter) يمكنك الوصول إلى كائن موجود عبر واجهة مختلفة. باستخدام [الوكيل (Proxy)](/design-patterns/proxy)، تظل الواجهة كما هي. باستخدام [المزخرف (Decorator)](/design-patterns/decorator) يمكنك الوصول إلى الكائن عبر واجهة محسنة.
* يعرّف [الواجهة (Facade)](/design-patterns/facade) واجهة جديدة للكائنات الموجودة، بينما يحاول [المهايئ (Adapter)](/design-patterns/adapter) جعل الواجهة الحالية قابلة للاستخدام. يلف _المهايئ_ عادةً كائناً واحداً فقط، بينما يعمل _الواجهة_ مع نظام فرعي كامل من الكائنات.
* تمتلك أنماط [الجسر (Bridge)](/design-patterns/bridge)، [الحالة (State)](/design-patterns/state)، [الاستراتيجية (Strategy)](/design-patterns/strategy) (وإلى حد ما [المهايئ (Adapter)](/design-patterns/adapter)) هياكل متشابهة جداً. في الواقع، تعتمد جميع هذه الأنماط على التركيب (composition)، وهو تفويض العمل لكائنات أخرى. ومع ذلك، فإنها جميعاً تحل مشكلات مختلفة. النمط ليس مجرد وصفة لهيكلة الكود الخاص بك بطريقة معينة. يمكنه أيضاً إبلاغ المطورين الآخرين بالمشكلة التي يحلها النمط.
## Relations

**Related patterns**

- [الجسر](/ar/design-patterns/bridge.md)
- [المُزَخرِف](/ar/design-patterns/decorator.md)
- [الوكيل](/ar/design-patterns/proxy.md)
- [الواجهة (Facade)](/ar/design-patterns/facade.md)
- [الحالة](/ar/design-patterns/state.md)
- [الاستراتيجية](/ar/design-patterns/strategy.md)

## Code Examples

### java

```java
package refactoring_guru.adapter.example.adapters;

import refactoring_guru.adapter.example.round.RoundPeg;
import refactoring_guru.adapter.example.square.SquarePeg;

/**
 * يسمح لك المهايئ بملاءمة الأوتاد المربعة في الثقوب المستديرة.
 */
public class SquarePegAdapter extends RoundPeg {
    private SquarePeg peg;

    public SquarePegAdapter(SquarePeg peg) {
        this.peg = peg;
    }

    @Override
    public double getRadius() {
        double result;
        // احسب نصف قطر دائرة صغرى تناسب هذا الوتد.
        result = (Math.sqrt(Math.pow((peg.getWidth() / 2), 2) * 2));
        return result;
    }
}
```

