تعرف على مبادئ SOLID وتطبيقها في البرمجة بشكل عملي

سمعنا جميعاً عن مبادئ SOLID الخمسة أثناء الاستعداد لمقابلات العمل، وحفظنا تعريفاتها النظرية عن ظهر قلب. لكن بمجرد العودة إلى بيئة العمل وكتابة الكود الفعلي، يتلاشى هذا الحفظ ونتفاجأ بـ "سباغيتي كود" معقد، يصعب التعديل عليه، وتتحول فيه إضافة ميزة بسيطة إلى كابوس يكسر أجزاءً أخرى من النظام. مبادئ SOLID ليست طقوساً أكاديمية، بل هي قواعد هندسية وُضعت لكي تجعل كودك قابلاً للتوسع، سهلاً في الصيانة، وصامداً أمام التغييرات المستمرة. في هذا الدليل العملي من منصة نوفيل، سنفكك المبادئ الخمسة بأمثلة حقيقية من أرض الواقع باستخدام Python.
القاعدة المباشرة: الكلاس أو الدالة يجب أن يكون لها سبب واحد وفقط واحد للتغيير.
انظر إلى هذا الكلاس، إنه يفعل كل شيء؛ يدير بيانات المستخدم، ويحفظ في قاعدة البيانات، ويرسل رسائل ترحيبية:
class UserManager:
def __init__(self, name, email):
self.name = name
self.email = email
def save_to_database(self):
print(f"تم حفظ {self.name} في قاعدة البيانات.")
def send_welcome_email(self):
print(f"تم إرسال إيميل ترحيبي إلى {self.email}")لماذا هذا الكود كارثي؟ إذا قررت مستقبلاً تغيير قاعدة البيانات من MySQL إلى MongoDB، أو تغيير مزود خدمة الإيميل، ستضطر لتعديل نفس الكلاس، مما قد يكسر منطق إدارة المستخدم نفسه.
نقوم بتفكيك المسؤوليات إلى كلاسات منفصلة ومستقلة تماماً:
class User:
def __init__(self, name, email):
self.name = name
self.email = email
class UserDB:
def save(self, user):
print(f"[قاعدة البيانات] تم حفظ {user.name} بنجاح.")
class EmailService:
def send_welcome(self, user):
print(f"[الإيميل] تم إرسال رسالة ترحيب إلى {user.email}")القاعدة المباشرة: الكود يجب أن يكون مفتوحاً للتوسع (إضافة ميزات جديدة)، ومغلقاً أمام التعديل (دون تغيير الكود القديم المستقر).
class PaymentManager:
def process_payment(self, amount, method):
if method == "credit_card":
print(f"خصم {amount} عبر بطاقة الائتمان")
elif method == "paypal":
print(f"خصم {amount} عبر بايبال")لماذا هذا الكود كارثي؟ إذا أردت إضافة طريقة دفع جديدة (مثل InstaPay أو فودافون كاش)، ستضطر لفتح هذا الكلاس وتعديله وإضافة elif جديدة. هذا التعديل قد يتسبب في حدوث خطأ (Bug) يفسد طرق الدفع القديمة المستقرة.
نستخدم الـ Abstraction (الوراثة أو الواجهات) بحيث نغلق الكلاس الرئيسي ونفتح المجال لإضافة كلاسات جديدة:
from abc import ABC, abstractmethod
# واجهة مجردة لطرق الدفع
class PaymentMethod(ABC):
@abstractmethod
def pay(self, amount):
pass
class CreditCardPayment(PaymentMethod):
def pay(self, amount):
print(f"خصم {amount} عبر بطاقة الائتمان")
class PaypalPayment(PaymentMethod):
def pay(self, amount):
print(f"خصم {amount} عبر بايبال")
# الكلاس الرئيسي مغلق تماماً ولا يتغير أبداً عند إضافة طرق دفع جديدة
class PaymentProcessor:
def process(self, payment_method: PaymentMethod, amount):
payment_method.pay(amount)القاعدة المباشرة: يجب أن تكون قادراً على استبدال الكلاس الأب بأي كلاس ابن يرث منه دون أن يتغير سلوك البرنامج أو ينهار الكود.
class Bird:
def fly(self):
return "أنا أطير في السماء!"
class Ostrich(Bird): # النعامة ترث من الطيور
def fly(self):
raise Exception("النعامة لا تستطيع الطيران!") # انهيار الكود!لماذا هذا الكود كارثي؟ إذا كانت لديك دالة تمر على مجموعة طيور وتجعلها تطير، بمجرد أن تصل إلى "النعامة" سينهار التطبيق تماماً (Crash) لأن الكلاس الابن خرق التوقعات ولم يلتزم بسلوك الأب.
لا تضع دالات في الكلاس الأب لا تستطيع جميع الأبناء تنفيذها. قسّم الوراثة بناءً على القدرات الفعلية:
class Bird:
def eat(self):
return "أنا آكل الطعام"
class FlyingBird(Bird):
def fly(self):
return "أنا أطير في السماء!"
class Duck(FlyingBird):
pass
class Ostrich(Bird): # النعامة تأكل ولكنها لا تطير، لذا ترث من Bird فقط
passالقاعدة المباشرة: لا تجبر كلاس على الاعتماد على واجهات أو دالات لا يستخدمها. من الأفضل إنشاء واجهات صغيرة ومحددة بدلاً من واجهة واحدة ضخمة.
class SmartDevice(ABC):
@abstractmethod
def print_document(self): pass
@abstractmethod
def fax_document(self): pass
class OldPrinter(SmartDevice):
def print_document(self):
print("طباعة الورقة...")
def fax_document(self):
# الطابعة القديمة لا تحتوي على فاكس! مجبرة على كتابة دالة فارغة
passقم بفصل الواجهات الكبيرة إلى أجزاء صغيرة متخصصة:
class Printer(ABC):
@abstractmethod
def print_document(self): pass
class Fax(ABC):
@abstractmethod
def fax_document(self): pass
# طابعة قديمة تنفذ الطباعة فقط
class OldPrinter(Printer):
def print_document(self):
print("طباعة الورقة...")
# طابعة حديثة خارقة تنفذ الاثنين معاً
class AdvancedMachine(Printer, Fax):
def print_document(self): print("طباعة...")
def fax_document(self): print("إرسال فاكس...")القاعدة المباشرة: الكلاسات ذات المستوى العالي (High-level) يجب ألا تعتمد على كلاسات ذات مستوى منخفض (Low-level). كلاهما يجب أن يعتمد على "التجريد" (Abstraction).
class MySQLDatabase:
def insert(self, data):
print("حفظ في MySQL")
class PasswordReset:
def __init__(self):
# الكلاس عالي المستوى يعتمد مباشرة وبشكل صارم على كلاس منخفض المستوى
self.db = MySQLDatabase()
def reset(self, email):
self.db.insert(email)لماذا هذا الكود كارثي؟ كلاس استعادة كلمة المرور مربوط ومقيد للأبد بـ MySQL. إذا قررت الانتقال لـ MongoDB، ستضطر لإعادة كتابة الكلاس بالكامل وتعديل الـ __init__.
نجعل كلاس استعادة كلمة المرور يتعامل مع "واجهة مجردة لقاعدة البيانات"، ويمرر له النظام أي قاعدة بيانات يريدها أثناء التشغيل (Dependency Injection):
class Database(ABC): # التجريد المشترك
@abstractmethod
def insert(self, data): pass
class MySQLDatabase(Database):
def insert(self, data): print("حفظ في MySQL")
class MongoDatabase(Database):
def insert(self, data): print("حفظ في MongoDB")
class PasswordReset:
# الكلاس يعتمد على الواجهة المجردة وليس على نوع محدد
def __init__(self, db: Database):
self.db = db
def reset(self, email):
self.db.insert(email)
# عند التشغيل يمكنك تبديل قواعد البيانات بكل حرية وبسطر واحد دون لمس كود الكلاس الأساسي!
reset_service = PasswordReset(MongoDatabase())تذكر دائماً أن مبادئ SOLID ليست قيوداً لتصعيب كتابة الكود البرمجي، بل هي استثمار ذكي لوقتك وجهدك. في البداية قد تشعر أنك تكتب ملفات وكلاسات أكثر، ولكن عندما يبدأ مشروعك في النمو والاتساع، ستشكر نفسك لأنك بنيت أساساً هندسياً مرناً يتيح لك تعديل أي جزء في كود الـ Backend أو الواجهات بسلاسة تامة ودون خوف من انهيار السيرفر!