اليوم رح نحكي عن الـ Abstract Class. مثل ما شفنا بدرس الماضي عن الـ Interfaces، رح تلاحظوا في كثير تشابه بين الثنتين.
خلينا أول شي نشوف شو هي المشكلة قبل ما نحكي عن الحل.
عندنا class اسمه CreditCardPayment لمعالجة الدفع. فيه:
balance - جاي من قاعدة البياناتpay - بتعمل process للـ payment، ممكن تكون عم تتكلم مع Credit Card APIvalidateTransaction - بتتأكد من صحة المبلغpublic class CreditCardPayment {
double balance;
public void pay(double amount) {
// process payment via Credit Card API
System.out.println("Paying " + amount + " via Credit Card");
}
public void validateTransaction(double amount) {
if (amount > 0 && amount <= balance) {
System.out.println
اشترك في النشرة البريدية
دروس جديدة، مقالات، وأدوات مباشرة لبريدك.
وعندنا كمان class اسمه PayPalPayment. فيه نفس الشغلات:
public class PayPalPayment {
double balance;
public void pay(double amount) {
// process payment via PayPal API
System.out.println("Paying " + amount + " via PayPal");
}
public void validateTransaction(double amount) {
if (amount > 0 && amount <= balance) {
System.out.println("Amount is valid");
} else {
System.out.println("Amount is not valid");
}
}
}بتشوفوا إن مثل validateTransaction هو نفس الكود بالضبط في الكلاسين.
عندنا class اسمه PaymentProcessor فيه مثلين:
public class PaymentProcessor {
public void processPayment(CreditCardPayment payment, double amount) {
payment.validateTransaction(amount);
payment.pay(amount);
}
public void processPayment(PayPalPayment payment, double amount) {
payment.validateTransaction(amount);
payment.pay(amount);
}
}وبالـ main:
public static void main(String[] args) {
PaymentProcessor processor = new PaymentProcessor();
CreditCardPayment creditCard = new CreditCardPayment();
PayPalPayment paypal = new PayPalPayment();
processor.processPayment(creditCard, 50);
processor.processPayment(paypal, 50);
}لو شغلنا التطبيق رح تطلعلنا:
Amount is valid
Paying 50 via Credit Card
Amount is valid
Paying 50 via PayPal
المشكلة الأولى في الـ PaymentProcessor: كل مرة بدنا نضيف طريقة دفع جديدة، لازم نضيف مثل جديد. الكود بيبلش يتعقد، خاصة إن الـ logic بالمثلين متشابه كثيرا.
المشكلة الثانية: الـ validateTransaction مكررة في كلاسين بنفس الـ logic تماما. ليش عم نكررها؟
السبب إن كل class مختلف عن الثاني، ما في رابط مشترك بينهم. لو كانوا متوحدين تحت class واحد، ما كنا بحاجة نكرر شي.
الـ Abstract Class هو class يجمع الـ related classes اللي إلها نفس الـ concept أو نفس الـ business تحت سقف واحد.
بالـ Abstract Class فينا نحدد شغلتين:
| النوع | الوصف |
|---|---|
| Abstract Methods | بنعرف اسم المثل وشو بيطلب، بس ما بنكتب الـ implementation |
| Default Implementation | كود مكرر بأكثر من مكان بنحطه هون مرة واحدة |
بننشئ class اسمه AbstractPayment ونعرفه كـ abstract:
public abstract class AbstractPayment {
double balance;
public abstract void pay(double amount);
public void validateTransaction(double amount) {
if (amount > 0 && amount <= balance) {
System.out.println("Amount is valid");
} else {
System.out.println("Amount is not valid");
}
}
}المثل pay هو abstract method - بعرف اسمه والـ parameters بس ما بعرف الـ implementation، لأن كل class بيطبقها بطريقة مختلفة.
المثل validateTransaction هو default implementation - نفس الكود لكل الكلاسين، فحطيناه بمكان واحد.
بنروح على CreditCardPayment ونعملها extends AbstractPayment:
public class CreditCardPayment extends AbstractPayment {
@Override
public void pay(double amount) {
System.out.println("Paying " + amount + " via Credit Card");
}
}إذا حذفنا مثل pay بيعطينا error:
Class
CreditCardPaymentmust either be declared abstract or implement abstract methodpay
يعني الكلاس أُرغم إنه يكتب الـ implementation للـ pay.
نفس الشي بالنسبة لـ PayPalPayment:
public class PayPalPayment extends AbstractPayment {
@Override
public void pay(double amount) {
System.out.println("Paying " + amount + " via PayPal");
}
}بتلاحظوا إن validateTransaction اختفت من الكلاسين. بس لأنهم عملوا extends للـ AbstractPayment، صار الكود موجود جواتهم تلقائيا بفضل الـ inheritance.
بما إن الكلاسين هلق موحدين تحت AbstractPayment، ما في داعي لمثلين منفصلين بالـ processor. فينا نحط مثل واحد بس:
public class PaymentProcessor {
public void processPayment(AbstractPayment payment, double amount) {
payment.validateTransaction(amount);
payment.pay(amount);
}
}بدل ما كان عندنا مثلين كل واحد يأخذ type مختلف، هلق عندنا مثل واحد يأخذ AbstractPayment. وبما إن CreditCardPayment وPayPalPayment كلهم من نوع AbstractPayment، فينا نمررهم للمثل هاد.
لو بدنا نضيف طريقة دفع جديدة مثل CryptoPayment، بنعمل:
public class CryptoPayment extends AbstractPayment {
@Override
public void pay(double amount) {
System.out.println("Paying " + amount + " via Crypto");
}
}وما في حاجة نعدل على الـ PaymentProcessor أبدا. لأن CryptoPayment هو من نوع AbstractPayment.
لو بدنا نعدل على الـ validateTransaction، بنروح لمكان واحد بس، وهو الـ AbstractPayment. قبل كنا لازم نعدل بكل class على حدا.
| الخاصية | interface | abstract class |
|---|---|---|
| Abstract methods | نعم، أساسية | نعم، أساسية |
| Default implementation | ممكن، بس آخر خيار | مخصصة لهاي الحالة |
| المتغيرات (fields) | لا | نعم |
الـ Interface مخصصة للـ signatures والعقود. الـ Abstract Class مخصصة للـ default implementation والكود المشترك بين الكلاسات المترابطة.