أهلاً! سنتحدث اليوم عن مفهوم مهم في 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()
: سيكون التنفيذ هو نفسه في كل فئة. بالمناسبة، لقد صادفت بالفعل واجهات في المهام السابقة، حتى لو لم تلاحظ ذلك :) إليك مثال حي: لقد عملت مع واجهات List
و Set
! بتعبير أدق، لقد عملت مع تطبيقاتها — ArrayList
، LinkedList
و، HashSet
و، وما إلى ذلك. ويعطي نفس المخطط بوضوح مثالاً حيث يقوم فصل واحد بتنفيذ واجهات متعددة في نفس الوقت. على سبيل المثال، LinkedList
تنفذ الواجهات List
و Deque
(قائمة الانتظار ذات النهاية المزدوجة). أنت على دراية بالواجهة Map
، أو بالأحرى، بتطبيقها HashMap
. بالمناسبة، يوضح هذا الرسم البياني ميزة: يمكن أن ترث الواجهات واجهات أخرى. SortedMap
ترث الواجهة Map
بينما Deque
ترث Queue
. يعد ذلك ضروريًا إذا كنت تريد إظهار العلاقة بين الواجهات، حيث تكون إحدى الواجهات عبارة عن نسخة موسعة من أخرى. دعونا نفكر في مثال للواجهة Queue
. لم نقم بمراجعتها بعد Queues
، ولكنها بسيطة نوعًا ما وتعمل مثل قائمة الانتظار أو الخط العادي في المتجر. يمكنك فقط إضافة عناصر إلى نهاية قائمة الانتظار، ولا يمكنك أخذها إلا من البداية. في مرحلة ما، احتاج المطورون إلى نسخة محسنة من قائمة الانتظار من أجل إضافة العناصر وأخذها من كلا الطرفين. لذلك قاموا بإنشاء Deque
واجهة، وهي عبارة عن قائمة انتظار ذات نهاية مزدوجة. لديه كل أساليب قائمة الانتظار العادية. بعد كل شيء، إنها أصل قائمة الانتظار ذات النهاية المزدوجة، ولكنها تضيف أيضًا أساليب جديدة.
GO TO FULL VERSION