CodeGym /مدونة جافا /Random-AR /لماذا نحتاج إلى واجهات في جافا؟
John Squirrels
مستوى
San Francisco

لماذا نحتاج إلى واجهات في جافا؟

نشرت في المجموعة
أهلاً! سنتحدث اليوم عن مفهوم مهم في Java: الواجهات. ربما تكون الكلمة مألوفة لك. على سبيل المثال، تحتوي معظم برامج وألعاب الكمبيوتر على واجهات. بالمعنى الواسع، الواجهة هي نوع من "جهاز التحكم عن بعد" الذي يربط بين طرفين متفاعلين. مثال بسيط للواجهة في الحياة اليومية هو جهاز التحكم عن بعد الخاص بالتلفزيون. فهو يربط بين كائنين - شخص وجهاز تلفزيون - ويؤدي مهام مختلفة: رفع مستوى الصوت أو خفضه، وتبديل القنوات، وتشغيل التلفزيون أو إيقاف تشغيله. يحتاج أحد الأطراف (الشخص) إلى الوصول إلى الواجهة (اضغط على زر في جهاز التحكم عن بعد) ليقوم الطرف الثاني بتنفيذ الإجراء. على سبيل المثال، لتغيير التلفزيون إلى القناة التالية. علاوة على ذلك، لا يحتاج المستخدم إلى معرفة كيفية تنظيم التلفزيون أو كيفية تنفيذ عملية تغيير القناة داخليًا. الشيء الوحيد الذي يمكن للمستخدم الوصول إليه هو الواجهة. الهدف الرئيسي هو الحصول على النتيجة المرجوة. ما علاقة هذا بالبرمجة وجافا؟ كل شيء :) إنشاء واجهة يشبه إلى حد كبير إنشاء فئة عادية، ولكن بدلاً من استخدام فئة الكلمة ، نشير إلى واجهة الكلمة . دعونا نلقي نظرة على أبسط واجهة Java، ونرى كيف تعمل، ولماذا نحتاج إليها:
public interface CanSwim {

     public void swim();
}
لقد أنشأنا واجهة CanSwim . إنه يشبه إلى حد ما جهاز التحكم عن بعد لدينا، ولكن مع "زر" واحد: طريقة السباحة () . ولكن كيف نستخدم جهاز التحكم عن بعد هذا؟ للقيام بذلك، نحن بحاجة إلى تنفيذ طريقة، أي زر التحكم عن بعد لدينا. لاستخدام واجهة، يجب على بعض الفئات في برنامجنا تنفيذ أساليبها. دعونا نخترع فئة "يمكن لأشياءها السباحة". على سبيل المثال، فئة Duck تناسب:
public class Duck implements CanSwim {

    public void swim() {
        System.out.println("Duck, swim!");
    }

    public static void main(String[] args) {

        Duck duck = new Duck();
        duck.swim();
    }
}
"ماذا نرى هنا؟ فئة Duck "مرتبطة" بواجهة CanSwim من خلال الكلمة الأساسية للأدوات . ربما تتذكر أننا استخدمنا آلية مماثلة لربط فئتين من خلال الميراث، ولكن في هذه الحالة استخدمنا الكلمة يمتد. بوضوح تام، يمكننا ترجمة " الطبقة العامة Duck تنفذ CanSwim " حرفيًا على النحو التالي: " تطبق فئة Duck العامة واجهة CanSwim ". وهذا يعني أن الفئة المرتبطة بالواجهة يجب أن تنفذ جميع أساليبها. ملاحظة: صفنا Duck، تمامًا مثل "الواجهة CanSwimلديها swim()طريقة وتحتوي على بعض المنطق. هذا مطلب إلزامي. إذا كتبنا للتو public class Duck implements CanSwimدون إنشاء swim()طريقة في Duckالفصل، فسوف يعطينا المترجم خطأ: Duck ليس مجردًا ولا يتجاوز طريقة السباحة المجردة () في CanSwim لماذا؟ لماذا يحدث هذا؟ إذا شرحنا الخطأ باستخدام مثال التلفزيون، فسيكون الأمر مثل إعطاء شخص ما جهاز التحكم عن بعد الخاص بالتلفزيون مع زر "تغيير القناة" الذي لا يمكنه تغيير القنوات. يمكنك الضغط على الزر بقدر ما تريد، لكنه لن ينجح. لا يقوم جهاز التحكم عن بعد بتغيير القنوات من تلقاء نفسه: فهو يرسل فقط إشارة إلى التلفزيون، الذي ينفذ العملية المعقدة لتغيير القناة. وهذا هو الحال مع البطة: يجب أن تعرف كيفية السباحة حتى يمكن استدعاؤها باستخدام الواجهة CanSwim. إذا لم يعرف كيف، فإن CanSwimالواجهة لن تربط الطرفين – الشخص والبرنامج. لن يتمكن الشخص من استخدام swim()الطريقة للسباحة Duckداخل البرنامج. أنت الآن تفهم بشكل أكثر وضوحًا الغرض من الواجهات. تصف الواجهة السلوك الذي يجب أن تتمتع به الفئات التي تنفذ الواجهة. "السلوك" هو مجموعة من الأساليب. إذا أردنا إنشاء العديد من برامج المراسلة، فإن أسهل ما يمكنك فعله هو إنشاء واجهة Messenger. ماذا يحتاج كل رسول؟ على المستوى الأساسي، يجب أن يكونوا قادرين على تلقي وإرسال الرسائل.
public interface Messenger{

     public void sendMessage();

     public void getMessage();
}
الآن يمكننا ببساطة إنشاء فئات المراسلة الخاصة بنا التي تنفذ الواجهة المقابلة. سوف "يجبرنا" المترجم نفسه على تنفيذها في فصولنا. برقية:
public class Telegram implements Messenger {

    public void sendMessage() {

        System.out.println("Sending a Telegram message!");
    }

     public void getMessage() {
         System.out.println("Receiving a Telegram message!");
     }
}
واتساب:
public class WhatsApp implements Messenger {

    public void sendMessage() {

        System.out.println("Sending a WhatsApp message!");
    }

     public void getMessage() {
         System.out.println("Reading a WhatsApp message!");
     }
}
فايبر:
public class Viber implements Messenger {

    public void sendMessage() {

        System.out.println("Sending a Viber message!");
    }

     public void getMessage() {
         System.out.println("Receiving a Viber message!");
     }
}
ما هي المزايا التي يوفرها هذا؟ وأهمها هو الاقتران السائب. تخيل أننا نصمم برنامجًا يجمع بيانات العميل. يحتاج الفصل Clientبالتأكيد إلى حقل للإشارة إلى برنامج المراسلة المحدد الذي يستخدمه العميل. بدون واجهات، سيبدو هذا غريبًا:
public class Client {

    private WhatsApp whatsApp;
    private Telegram telegram;
    private Viber viber;
}
لقد قمنا بإنشاء ثلاثة حقول، ولكن يمكن أن يكون لدى العميل برنامج مراسلة واحد فقط. نحن فقط لا نعرف أي واحد. لذلك يتعين علينا إضافة كل الإمكانيات إلى الفصل حتى نتمكن من التواصل مع العميل. لقد اتضح أن واحدًا أو اثنين منهم سيكونان دائمًا nullغير ضروريين تمامًا للبرنامج. من الأفضل استخدام واجهتنا بدلاً من ذلك:
public class Client {

    private Messenger messenger;
}
وهذا مثال على الاقتران السائب! بدلاً من تحديد فئة مراسلة معينة في الفصل Client، نشير فقط إلى أن العميل لديه رسول. أيهما سيتم تحديده بالضبط أثناء تشغيل البرنامج. لكن لماذا نحتاج إلى واجهات لهذا؟ ولماذا تمت إضافتهم أصلاً إلى اللغة؟ هذا سؤال جيد – والسؤال الصحيح! ألا يمكننا تحقيق نفس النتيجة باستخدام الميراث العادي؟ الفصل Messengerكوالد و و Viberو Telegramكأطفال WhatsApp. في الواقع، هذا ممكن. ولكن هناك عقبة واحدة. كما تعلمون، جافا ليس لديها وراثة متعددة. ولكن هناك دعم للواجهات المتعددة. يمكن للفصل تنفيذ أي عدد تريده من الواجهات. تخيل أن لدينا Smartphoneفصلًا يحتوي على Appحقل واحد يمثل تطبيقًا مثبتًا على الهاتف الذكي.
public class Smartphone {

    private App app;
}
بالطبع، التطبيق وبرنامج المراسلة متشابهان، لكنهما لا يزالان شيئان مختلفان. يمكن أن يكون هناك إصدارات للهواتف المحمولة وإصدارات سطح المكتب لبرنامج المراسلة، ولكن التطبيق يمثل على وجه التحديد تطبيقًا للجوال. هذه هي الصفقة - إذا استخدمنا الوراثة، فلن نتمكن من إضافة Telegramكائن إلى Smartphoneالفصل. بعد كل شيء، Telegramلا يمكن للفصل أن يرث Appو Messenger! وقد جعلناها ترث بالفعل Messengerوأضفناها إلى Clientالفصل. لكن Telegramيمكن للفصل بسهولة تنفيذ كلتا الواجهتين! وفقًا لذلك، يمكننا أن نعطي الفصل Clientكائنًا Telegramككائن Messenger، ويمكننا أن نعطيه للفئة Smartphoneككائن App. إليك كيفية القيام بذلك:
public class Telegram implements Application, Messenger {

    // ...methods
}

public class Client {

    private Messenger messenger;

    public Client() {
        this.messenger = new Telegram();
    }
}


public class Smartphone {

    private Application application;

    public Smartphone() {
        this.application = new Telegram();
    }
}
الآن نحن نستخدم Telegramالفصل بالطريقة التي نريدها. في بعض الأماكن، يعمل بمثابة App. وفي أماكن أخرى، يعمل بمثابة Messenger. من المؤكد أنك لاحظت بالفعل أن طرق الواجهة تكون دائمًا "فارغة"، أي أنها لا تحتوي على أي تطبيق. والسبب في ذلك بسيط: تصف الواجهة السلوك، لكنها لا تنفذه. "يجب أن تكون جميع الكائنات التي تنفذ الواجهة CanSwimقادرة على السباحة": هذا كل ما تخبرنا به الواجهة. إن الطريقة المحددة التي تسبح بها الأسماك والبط والخيول هي سؤال للفئات و Fishو Duckو Horse، وليس الواجهة. تمامًا مثل تغيير القناة، فهي مهمة للتلفزيون. جهاز التحكم عن بعد يمنحك ببساطة زرًا لذلك. ومع ذلك، ظهرت إضافة مثيرة للاهتمام في Java 8 - الأساليب الافتراضية. على سبيل المثال، تحتوي واجهتك على 10 طرق. 9 منها لها تطبيقات مختلفة في فئات مختلفة، ولكن يتم تنفيذ واحد منها بنفس الطريقة للجميع. في السابق، قبل Java 8، لم يكن لطرق الواجهة أي تنفيذ على الإطلاق: أعطى المترجم خطأً على الفور. الآن يمكنك القيام بشيء مثل هذا:
public interface CanSwim {

   public default void swim() {
       System.out.println("Swim!");
   }

   public void eat();

   public void run();
}
باستخدام defaultالكلمة الأساسية، قمنا بإنشاء طريقة واجهة مع تطبيق افتراضي. نحن بحاجة إلى توفير التنفيذ الخاص بنا لطريقتين أخريين - eat()و run()- في جميع الفئات التي تنفذ CanSwim. لا نحتاج إلى القيام بذلك باستخدام الطريقة swim(): سيكون التنفيذ هو نفسه في كل فئة. بالمناسبة، لقد صادفت بالفعل واجهات في المهام السابقة، حتى لو لم تلاحظ ذلك :) إليك مثال حي: لقد لماذا تعتبر الواجهات ضرورية في Java - 2عملت مع واجهات Listو Set! بتعبير أدق، لقد عملت مع تطبيقاتها — ArrayList، LinkedListو، HashSetو، وما إلى ذلك. ويعطي نفس المخطط بوضوح مثالاً حيث يقوم فصل واحد بتنفيذ واجهات متعددة في نفس الوقت. على سبيل المثال، LinkedListتنفذ الواجهات Listو Deque(قائمة الانتظار ذات النهاية المزدوجة). أنت على دراية بالواجهة Map، أو بالأحرى، بتطبيقها HashMap. بالمناسبة، يوضح هذا الرسم البياني ميزة: يمكن أن ترث الواجهات واجهات أخرى. SortedMapترث الواجهة Mapبينما Dequeترث Queue. يعد ذلك ضروريًا إذا كنت تريد إظهار العلاقة بين الواجهات، حيث تكون إحدى الواجهات عبارة عن نسخة موسعة من أخرى. دعونا نفكر في مثال للواجهة Queue. لم نقم بمراجعتها بعد Queues، ولكنها بسيطة نوعًا ما وتعمل مثل قائمة الانتظار أو الخط العادي في المتجر. يمكنك فقط إضافة عناصر إلى نهاية قائمة الانتظار، ولا يمكنك أخذها إلا من البداية. في مرحلة ما، احتاج المطورون إلى نسخة محسنة من قائمة الانتظار من أجل إضافة العناصر وأخذها من كلا الطرفين. لذلك قاموا بإنشاء Dequeواجهة، وهي عبارة عن قائمة انتظار ذات نهاية مزدوجة. لديه كل أساليب قائمة الانتظار العادية. بعد كل شيء، إنها أصل قائمة الانتظار ذات النهاية المزدوجة، ولكنها تضيف أيضًا أساليب جديدة.
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION