اليوم رح نحكي عن الـ interface في Java، بس أول شي خلينا نفهم المشكلة اللي بتخلينا نحتاجها.
تخيل عندك نظام دفع فيه طريقتين مختلفتين. الأولى CreditCardPayment والثانية PayPalPayment.
public class CreditCardPayment {
public void pay(double amount) {
System.out.println("Processing credit card payment: " + amount);
}
}
public class PayPalPayment {
public void pay(double amount) {
System.out.println("Processing PayPal payment: " + amount);
}
}
الاثنين فيهم method اسمها pay وبتاخذ amount كـ double. هالتشابه هو بالضبط المشكلة.
حاليا عندنا class اسمه PaymentProcessor، وفيه method لكل طريقة دفع:
اشترك في النشرة البريدية
دروس جديدة، مقالات، وأدوات مباشرة لبريدك.
public class PaymentProcessor {
public void processCreditCardPayment(CreditCardPayment payment, double amount) {
payment.pay(amount);
}
public void processPayPalPayment(PayPalPayment payment, double amount) {
payment.pay(amount);
}
}وبالـ main بتستخدمها هيك:
PaymentProcessor processor = new PaymentProcessor();
CreditCardPayment creditCard = new CreditCardPayment();
PayPalPayment paypal = new PayPalPayment();
processor.processCreditCardPayment(creditCard, 100.0);
processor.processPayPalPayment(paypal, 50.0);كل شي بيشتغل، بس في مشاكل واضحة.
الـ methods بالـ PaymentProcessor كلها تقريبا نفس الشي:
| الجانب | القيمة |
|---|---|
| Return type | void |
| Parameters | طريقة الدفع + double amount |
| الكود الداخلي | .pay(amount) |
إذا بدك تدعم طريقة دفع ثالثة أو رابعة، لازم تضيف method جديدة في كل مرة. هذا بيخلي الكود صعب الـ maintaining وبيجيب مشاكل كثيرة مع الوقت.
الحل يبدأ بسؤال بسيط: شو هو الشي المشترك بين هالكلاسات؟
الجواب: كل طريقة دفع عندها method اسمها pay وبتاخذ amount كـ double.
الـ interface تفكر فيه كعقد بين طرفين. الطرف الأول هو الـ interface نفسه، والطرف الثاني هو الـ class اللي بينفذه.
الـ interface ما فيه كود تنفيذي. كل اللي يعرفه هو:
الكلاس اللي بيوقع على هالعقد لازم يلتزم بكل شي جوا الـ interface.
بننشئ interface جديد اسمه PaymentMethod:
public interface PaymentMethod {
void pay(double amount);
}بس هيك. ما في body للـ method، بس الـ signature.
الآن نروح على CreditCardPayment ونخليها implements هالـ interface:
public class CreditCardPayment implements PaymentMethod {
@Override
public void pay(double amount) {
System.out.println("Processing credit card payment: " + amount);
}
}لاحظ implements مش extends. هذا هو الفرق، الكلاس بينفذ عقد مش بيرث من parent.
لو حذفت الـ method أو غيرت الـ signature، الـ compiler رح يوقفك ويطلب منك تلتزم بالعقد.
ونفس الشي للـ PayPalPayment:
public class PayPalPayment implements PaymentMethod {
@Override
public void pay(double amount) {
System.out.println("Processing PayPal payment: " + amount);
}
}الآن بدل ما يكون عندنا method لكل طريقة دفع، بنعمل method واحدة تتعامل مع أي PaymentMethod:
public class PaymentProcessor {
public void processPayment(PaymentMethod payment, double amount) {
payment.pay(amount);
}
}وبالـ main:
PaymentProcessor processor = new PaymentProcessor();
PaymentMethod creditCard = new CreditCardPayment();
PaymentMethod paypal = new PayPalPayment();
processor.processPayment(creditCard, 100.0);
processor.processPayment(paypal, 50.0);الـ type صار PaymentMethod بدل ما يكون CreditCardPayment أو PayPalPayment مباشرة. أي كلاس بيطبق الـ interface هذا بيقبله.
فيك تحط method فيها implementation جوا الـ interface باستخدام كلمة default:
public interface PaymentMethod {
void pay(double amount);
default void printReceipt() {
System.out.println("Payment processed.");
}
}بس هالشي لا ينصح به كحل أساسي. السبب الوحيد إن Java بتدعمه هو للـ backward compatibility، يعني لو عندك codebase كبير وصعب تعدل على كل الكلاسات، الـ default بيكون حل مؤقت.
الأفضل دايما تعتبر الـ interface كعقد بين الكلاسات، وما يكون فيه كود تنفيذي.
الـ interface بيحل مشكلة التكرار لما يكون عندك كلاسات مختلفة فيها نفس الـ behavior.
interface بيعرف الـ contract بدون كود تنفيذيimplements لينفذ الـ interface