مقدمة: لماذا تعتبر مبادئ SOLID مهمة لمطور Swift
التطوير لنظامي iOS و macOS لا يتطلب فقط معرفة الأطر (frameworks)، بل يتطلب أيضاً القدرة على إنشاء بنية قابلة للصيانة والاختبار والتوسع. هنا تأتي مبادئ SOLID — القواعد الخمس الأساسية للتصميم الكائني التوجُّه (object-oriented design) التي صاغها روبرت مارتن (العم بوب).
في هذه المقالة، سنشرح كل مبدأ من مبادئ SOLID الخمسة مع أمثلة كود بلغة Swift، ونعرض الأنماط المعاكسة الشائعة (antipatterns) ونقدم الحلول الصحيحة. بعد القراءة، ستتمكن من كتابة كود يسهل صيانته وتوسيعه واختباره.
1. مبدأ المسؤولية الواحدة (Single Responsibility Principle - SRP)
الصياغة: يجب أن يكون للفصل (class) سبب واحد فقط للتغيير. بمعنى آخر، كل وحدة (module) مسؤولة عن وظيفة واحدة بالضبط.
المشكلة: الفصل العملاق (Monster Class)
لنأخذ مثالاً نموذجياً من تطوير iOS — الفصل UserManager الذي يفعل الكثير جداً:
class UserManager { func fetchUser() -> User { /* طلب شبكة */ } func saveUserToDatabase(_ user: User) { /* العمل مع CoreData */ } func formatUserDisplayName(_ user: User) -> String { /* تنسيق */ } func sendWelcomeEmail(to user: User) { /* إرسال بريد إلكتروني */ }}هنا، الفصل مسؤول عن طلبات الشبكة، وقاعدة البيانات، وتنسيق واجهة المستخدم، وإرسال البريد الإلكتروني. أي تغيير في أي من هذه الجوانب سيؤدي إلى تغيير UserManager.
الحل: تقسيم المسؤوليات
class UserNetworkService { func fetchUser() -> User { /* طلب شبكة فقط */ }}
class UserDatabaseService { func saveUser(_ user: User) { /* العمل مع قاعدة البيانات فقط */ }}
class UserFormatter { func displayName(for user: User) -> String { /* تنسيق فقط */ }}
class EmailService { func sendWelcome(to user: User) { /* إرسال بريد إلكتروني فقط */ }}الآن كل فصل لديه مسؤولية واحدة واضحة، مما يبسط الاختبار والتعديل.
2. مبدأ الانفتاح/الإغلاق (Open/Closed Principle - OCP)
الصياغة: يجب أن تكون الكيانات البرمجية مفتوحة للتمديد (extension) ولكن مغلقة للتعديل (modification).
المشكلة: منطق صلب (Rigid Logic)
لنفترض أن لدينا فصلاً لحساب الخصومات:
class DiscountCalculator { func calculateDiscount(for order: Order, customerType: String) -> Double { if customerType == "regular" { return order.total * 0.05 } else if customerType == "premium" { return order.total * 0.1 } else if customerType == "vip" { return order.total * 0.2 } return 0 }}عند إضافة نوع عميل جديد (gold)، سنضطر لتعديل الفصل الحالي، مما ينتهك مبدأ OCP.
الحل: البروتوكولات وتعدد الأشكال (Polymorphism)
protocol DiscountStrategy { func discount(for order: Order) -> Double}
struct RegularDiscount: DiscountStrategy { func discount(for order: Order) -> Double { order.total * 0.05 }}
struct PremiumDiscount: DiscountStrategy { func discount(for order: Order) -> Double { order.total * 0.1 }}
struct VIPDiscount: DiscountStrategy { func discount(for order: Order) -> Double { order.total * 0.2 }}
class DiscountCalculator { func calculateDiscount(for order: Order, using strategy: DiscountStrategy) -> Double { return strategy.discount(for: order) }}الآن، لإضافة خصم جديد، يكفي إنشاء هيكل (struct) جديد يطبق البروتوكول DiscountStrategy، دون تعديل DiscountCalculator.
3. مبدأ استبدال ليسكوف (Liskov Substitution Principle - LSP)
الصياغة: يجب أن تكون الكائنات من الفئة الفرعية قابلة للاستبدال بكائنات من الفئة الأساسية دون التأثير على صحة البرنامج.