مرحبًا! سنتحدث اليوم عن مفهوم مهم في Java وهو الواجهات. ربما تكون الكلمة مألوفة لك. على سبيل المثال، تحتوي معظم برامج وألعاب الكمبيوتر على واجهات. بالمعنى الواسع، الواجهة هي نوع من "جهاز التحكم عن بعد" الذي يربط طرفين يتفاعلان مع بعضهما البعض. مثال بسيط لواجهة من الحياة اليومية هو جهاز التحكم عن بعد الخاص بالتلفزيون. فهو يربط بين كائنين، شخص وجهاز تلفزيون، ويقوم بمهام مختلفة: رفع مستوى الصوت أو خفضه، وتغيير القنوات، وتشغيل التلفزيون أو إيقاف تشغيله. يحتاج أحد الجانبين (الشخص) إلى الوصول إلى الواجهة (اضغط على زر التحكم عن بعد) حتى يتمكن الجانب الآخر من تنفيذ الإجراء. على سبيل المثال، لكي يقوم التلفزيون بتحويل القناة إلى القناة التالية. وفي هذه الحالة لا يحتاج المستخدم إلى معرفة جهاز التلفزيون وكيفية تنفيذ عملية تغيير القناة بداخله. كل ما يمكن للمستخدم الوصول إليه هو الواجهة . المهمة الرئيسية هي الحصول على النتيجة المرجوة. ما علاقة هذا بالبرمجة وجافا؟ مباشر :) إنشاء واجهة يشبه إلى حد كبير إنشاء فئة عادية، ولكن بدلا من الكلمة
class
نحدد الكلمة interface
. دعونا نلقي نظرة على أبسط واجهة Java ونكتشف كيف تعمل وما هو المطلوب من أجله:
public interface Swimmable {
public void swim();
}
لقد أنشأنا واجهة Swimmable
يمكنها السباحة . هذا يشبه جهاز التحكم عن بعد الخاص بنا، والذي يحتوي على "زر" واحد: الطريقة swim()
هي "السباحة". كيف يمكننا استخدام هذا " جهاز التحكم عن بعد "؟ ولهذا الغرض الطريقة، أي. يجب تنفيذ الزر الموجود على جهاز التحكم عن بعد لدينا. لاستخدام واجهة ما، يجب تنفيذ أساليبها بواسطة بعض فئات برنامجنا. دعونا نتوصل إلى فصل دراسي تتناسب أغراضه مع الوصف "يستطيع السباحة". على سبيل المثال، فئة البط مناسبة Duck
:
public class Duck implements Swimmable {
public void swim() {
System.out.println("Duck, swim!");
}
public static void main(String[] args) {
Duck duck = new Duck();
duck.swim();
}
}
ما الذي نراه هنا؟ يرتبط الفصل Duck
بواجهة Swimmable
باستخدام الكلمة الأساسية implements
. إذا كنت تتذكر، فقد استخدمنا آلية مماثلة لربط فئتين في الميراث، فقط كانت هناك كلمة " يمتد ". يمكن ترجمة " public class Duck implements Swimmable
" حرفيًا من أجل الوضوح: "الطبقة العامة Duck
تنفذ الواجهة Swimmable
." هذا يعني أن الفئة المرتبطة بالواجهة يجب أن تنفذ جميع أساليبها. يرجى ملاحظة: في فصلنا، Duck
تمامًا كما هو الحال في الواجهة ، Swimmable
توجد طريقة swim()
، ويوجد بداخلها نوع من المنطق. هذا هو شرط إلزامي. إذا كتبنا للتو " public class Duck implements Swimmable
" ولم ننشئ طريقة swim()
في الفصل Duck
، فسيعطينا المترجم خطأ: Duck ليس مجردًا ولا يتجاوز الطريقة المجردة swim() في Swimmable لماذا يحدث هذا؟ إذا شرحنا الخطأ باستخدام مثال التلفزيون، يتبين أننا نعطي شخصًا جهاز تحكم عن بعد مزودًا بزر "تغيير القناة" من تلفزيون لا يعرف كيفية تغيير القنوات. عند هذه النقطة، اضغط على الزر بقدر ما تريد، لن يعمل شيء. جهاز التحكم عن بعد نفسه لا يغير القنوات: فهو يعطي فقط إشارة إلى التلفزيون، حيث يتم تنفيذ عملية معقدة لتغيير القناة. وهذا هو الحال مع بطتنا: يجب أن تكون قادرة على السباحة بحيث يمكن الوصول إليها باستخدام الواجهة Swimmable
. إذا لم تكن تعرف كيفية القيام بذلك، فلن تقوم الواجهة Swimmable
بتوصيل الجانبين - الشخص والبرنامج. لن يتمكن أي شخص من استخدام طريقة 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 message to Telegram!");
}
public void getMessage() {
System.out.println("Reading the message in Telegram!");
}
}
واتساب:
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 message to Viber!");
}
public void getMessage() {
System.out.println("Reading a message in Viber!");
}
}
ما هي الفوائد التي يوفرها هذا؟ وأهمها هو الاقتران السائب. تخيل أننا نقوم بتصميم برنامج نقوم من خلاله بجمع بيانات العملاء. يجب أن يحتوي الفصل 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
الورثة. في الواقع، من الممكن القيام بذلك. ولكن هناك صيد واحد. كما تعلم، لا يوجد وراثة متعددة في Java. ولكن هناك تطبيقات متعددة للواجهات. يمكن للفصل تنفيذ أي عدد تريده من الواجهات. تخيل أن لدينا فصلًا Smartphone
به مجال Application
- تطبيق مثبت على هاتف ذكي.
public class Smartphone {
private Application application;
}
التطبيق والرسول متشابهان بالطبع، لكنهما شيئان مختلفان. يمكن أن يكون Messenger على الهاتف المحمول وعلى سطح المكتب، في حين أن التطبيق هو تطبيق على الهاتف المحمول. لذا، إذا استخدمنا الوراثة، فلن نتمكن من إضافة كائن Telegram
إلى الفصل Smartphone
. بعد كل شيء، لا يمكن للفصل Telegram
أن يرث من Application
ومن Messenger
! وقد تمكنا بالفعل من وراثتها Messenger
وإضافتها إلى الفصل بهذا النموذج Client
. لكن يمكن للفصل Telegram
تنفيذ كلتا الواجهتين بسهولة! لذلك، في الفصل Client
يمكننا تنفيذ كائن Telegram
كـ Messenger
، وفي الفصل Smartphone
كـ Application
. وإليك كيف يتم ذلك:
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
كما نشاء. في مكان ما سوف يتصرف في دور Application
, في مكان ما في دور Messenger
. ربما لاحظت بالفعل أن الأساليب في الواجهات تكون دائمًا "فارغة"، أي أنها لا تحتوي على أي تطبيق. والسبب في ذلك بسيط: فالواجهة تصف السلوك، ولا تنفذه. "يجب أن تكون جميع كائنات الفئات التي تنفذ الواجهة Swimmable
قادرة على التعويم": هذا كل ما تخبرنا به الواجهة. كيف ستسبح السمكة أو البطة أو الحصان بالضبط هو سؤال للفصول الدراسية Fish
وليس Duck
للواجهة Horse
. تماما مثل تغيير القناة هي مهمة التلفزيون. جهاز التحكم عن بعد يمنحك ببساطة زرًا للقيام بذلك. ومع ذلك، لدى Java8 إضافة مثيرة للاهتمام - الأساليب الافتراضية. على سبيل المثال، تحتوي واجهتك على 10 طرق. يتم تنفيذ 9 منها بشكل مختلف في فئات مختلفة، ولكن يتم تنفيذ واحد منها بنفس الطريقة في الكل. في السابق، قبل إصدار Java8، لم يكن للطرق الموجودة داخل الواجهات أي تنفيذ على الإطلاق: ألقى المترجم خطأً على الفور. الآن يمكنك القيام بذلك على النحو التالي:
public interface Swimmable {
public default void swim() {
System.out.println("Swim!");
}
public void eat();
public void run();
}
باستخدام الكلمة الأساسية default
، قمنا بإنشاء طريقة في الواجهة مع تطبيق افتراضي. سنحتاج إلى تنفيذ الطريقتين الأخريين، eat()
وأنفسنا run()
في جميع الفئات التي سيتم تنفيذها Swimmable
. ليست هناك حاجة للقيام بذلك باستخدام الطريقة swim()
: سيكون التنفيذ هو نفسه في جميع الفئات. بالمناسبة، لقد صادفت واجهات أكثر من مرة في المهام السابقة، على الرغم من أنك لم تلاحظ ذلك بنفسك :) إليك مثال واضح: لقد عملت مع واجهات List
و Set
! بتعبير أدق، مع تطبيقاتها - ArrayList
و LinkedList
، HashSet
وغيرها. يُظهر الرسم البياني نفسه مثالاً عندما يقوم فصل واحد بتنفيذ عدة واجهات في وقت واحد. على سبيل المثال، LinkedList
يقوم بتنفيذ الواجهات List
و Deque
(قائمة الانتظار ذات الوجهين). أنت أيضًا على دراية بالواجهة Map
، أو بالأحرى، بتطبيقاتها - HashMap
. بالمناسبة، في هذا المخطط يمكنك رؤية ميزة واحدة: يمكن وراثة الواجهات من بعضها البعض. الواجهة SortedMap
موروثة من قائمة الانتظار Map
وموروثة Deque
من قائمة الانتظار Queue
. يعد ذلك ضروريًا إذا كنت تريد إظهار الاتصال بين الواجهات، ولكن إحدى الواجهات هي نسخة موسعة من أخرى. لنلقِ نظرة على مثال للواجهة Queue
- قائمة الانتظار. لم نقم بمراجعة المجموعات بعد Queue
، لكنها بسيطة للغاية ومرتبة مثل الخط العادي في المتجر. يمكنك إضافة عناصر فقط إلى نهاية قائمة الانتظار، وإزالتها من البداية فقط. في مرحلة معينة، كان المطورون بحاجة إلى نسخة موسعة من قائمة الانتظار بحيث يمكن إضافة العناصر واستلامها من كلا الجانبين. هذه هي الطريقة التي تم بها إنشاء الواجهة Deque
- قائمة انتظار ثنائية الاتجاه. يحتوي على كافة أساليب قائمة الانتظار العادية، لأنه "الأصل" لقائمة انتظار ذات اتجاهين، ولكن تم إضافة أساليب جديدة.
GO TO FULL VERSION