الوراثة والبروتوكولات في Swift: protocol و extension
في درس الهياكل والكلاسات ورثت Cat من Animal، وعرفت أن الوراثة للكلاسات فقط. فكيف تتشارك الهياكل سلوكاً موحداً إذن؟ الجواب هو البروتوكولات (Protocols)، وهي ما يسمى في لغات أخرى «الواجهات». في هذا الدرس ستتعمق في الوراثة، ثم تتعلم البروتوكولات والإضافات، وهي الأسلوب المفضل في Swift لتنظيم الكود.
الوراثة بعمق: super و init
Section titled “الوراثة بعمق: super و init”الكلاس الوارث يستطيع إضافة خصائص جديدة، وأن يبني على دوال أبيه بدل استبدالها:
class Employee { let name: String let salary: Double
init(name: String, salary: Double) { self.name = name self.salary = salary }
func bonus() -> Double { salary * 0.10 }
func describe() -> String { "\(name) - المكافأة: \(bonus())" }}
class Manager: Employee { let teamSize: Int
init(name: String, salary: Double, teamSize: Int) { self.teamSize = teamSize super.init(name: name, salary: salary) }
override func bonus() -> Double { super.bonus() + 500 }}
let staff: [Employee] = [ Employee(name: "هدى", salary: 4000), Manager(name: "كريم", salary: 6000, teamSize: 5),]for person in staff { print(person.describe())}هدى - المكافأة: 400.0كريم - المكافأة: 1100.0super.init(...): استدعاء مُنشئ الأب. في Swift يجب أن تهيئ خصائص الابن أولاً ثم تستدعيsuper.init.super.bonus(): نسخة الأب من الدالة، ونضيف عليها.describeمكتوبة مرة واحدة في الأب، ومع ذلك استخدمت مكافأة المدير الصحيحة. هذا هو تعدد الأشكال (Polymorphism): الكائن الحقيقي يحدد أي نسخة تعمل.
البروتوكول protocol
Section titled “البروتوكول protocol”البروتوكول قائمة متطلبات: خصائص ودوال يتعهد النوع بتوفيرها. وميزته الكبرى أنه يعمل مع struct و class معاً:
protocol Shape { var name: String { get } func area() -> Double}
struct Rectangle: Shape { let name = "مستطيل" let width: Double let height: Double
func area() -> Double { width * height }}
struct Circle: Shape { let name = "دائرة" let radius: Double
func area() -> Double { 3.14 * radius * radius }}
let shapes: [any Shape] = [ Rectangle(width: 4, height: 5), Circle(radius: 10),]for shape in shapes { print("\(shape.name): \(shape.area())")}مستطيل: 20.0دائرة: 314.0var name: String { get }: البروتوكول يطلب خاصية قابلة للقراءة، ولا يهمه كيف توفرها.struct Rectangle: Shape: الهيكل يطابق البروتوكول، والمترجم يرفضه إن نسي أي متطلب.[any Shape]: مصفوفة تقبل أي نوع يطابقShape. الكلمةanyتعني «قيمة من نوع ما يطابق هذا البروتوكول».
البروتوكول مثل رخصة القيادة: لا يهم من أنت ولا من أي عائلة، المهم أنك أثبتّ قدرتك على القيادة.
والنوع الواحد يطابق أي عدد من البروتوكولات، بعكس الوراثة التي تسمح بأب واحد فقط:
protocol Playable { func play()}
protocol Shareable { func share()}
struct Song: Playable, Shareable { let title: String
func play() { print("تشغيل \(title)") }
func share() { print("مشاركة \(title)") }}
let song = Song(title: "نشيد الصباح")song.play()song.share()تشغيل نشيد الصباحمشاركة نشيد الصباحالإضافات extension
Section titled “الإضافات extension”extension تضيف دوالاً إلى نوع موجود، حتى لو لم تكتبه أنت، مثل Int و String:
extension Int { var isEven: Bool { self % 2 == 0 }
func squared() -> Int { self * self }}
print(7.isEven)print(7.squared())false49والأقوى: الإضافة على بروتوكول تعطي كل الأنواع المطابقة له دالة جاهزة دون أن تكتبها في كل نوع:
protocol Greeter { var name: String { get }}
extension Greeter { func greet() { print("مرحباً، أنا \(name)") }}
struct Teacher: Greeter { let name: String}
struct Robot: Greeter { let name: String
func greet() { print("بيب بوب. الاسم: \(name)") }}
Teacher(name: "منى").greet()Robot(name: "R2").greet()مرحباً، أنا منىبيب بوب. الاسم: R2Teacher حصل على greet مجاناً، و Robot كتب نسخته الخاصة فاستُخدمت بدلاً من الافتراضية. بهذا الأسلوب تتشارك الهياكل الكود دون وراثة.
بروتوكولات جاهزة من Swift
Section titled “بروتوكولات جاهزة من Swift”Swift نفسها مبنية على بروتوكولات، وهذه ثلاثة ستستخدمها كثيراً:
struct Point: Equatable, CustomStringConvertible { let x: Int let y: Int
var description: String { "(\(x), \(y))" }}
let p1 = Point(x: 2, y: 3)let p2 = Point(x: 2, y: 3)print(p1 == p2)print(p1)true(2, 3)| البروتوكول | ماذا يمنحك |
|---|---|
Equatable |
المقارنة بـ ==، وتكتبها Swift عنك تلقائياً للهياكل البسيطة |
Comparable |
المقارنة بـ < و >، فتعمل sorted() مع نوعك |
CustomStringConvertible |
شكل الطباعة عبر الخاصية description |
فحص النوع: is و as?
Section titled “فحص النوع: is و as?”مصفوفة من نوع الأب أو البروتوكول قد تحمل أنواعاً مختلفة. is تفحص النوع، و as? تحاول التحويل وتُرجع قيمة اختيارية:
for person in staff { if let manager = person as? Manager { print("\(manager.name) يدير \(manager.teamSize) موظفين") } else { print("\(person.name) موظف") }}print(staff[1] is Manager)هدى موظفكريم يدير 5 موظفينtrueبعد نجاح as? Manager أصبح بإمكاننا قراءة teamSize التي لا يعرفها النوع Employee.
وراثة أم بروتوكول؟
Section titled “وراثة أم بروتوكول؟”| الوراثة | البروتوكول | |
|---|---|---|
| يعمل مع | class فقط |
struct و class و enum |
| العدد المسموح | أب واحد | أي عدد |
| يحمل بيانات مخزنة | نعم | لا، متطلبات فقط |
| مشاركة الكود | من الأب | من extension على البروتوكول |
توصية Apple: ابدأ بالبروتوكول. واختر الوراثة فقط عندما تحتاج كلاسات تتشارك بيانات مخزنة، أو حين يفرضها إطار العمل مثل UIKit.
جرّب بنفسك
Section titled “جرّب بنفسك”- أنشئ
protocol Vehicleفيهnameوwheels، وهيكلينCarوBikeيطابقانه. - أضف
extension Vehicleفيها دالةinfo()تطبع الاسم وعدد العجلات، وجرّبها على مصفوفة[any Vehicle]. - أنشئ
protocol Electricفيهfunc charge()، وهيكلElectricCarيطابق البروتوكولين معاً. - تحدٍّ: أضف
extension Stringفيها خاصية محسوبةwordCountتُرجع عدد الكلمات في النص.
أخطاء شائعة
Section titled “أخطاء شائعة”- نسيان متطلب: ستقرأ
Type 'Circle' does not conform to protocol 'Shape'. زر الإصلاح في Xcode يضيف الدوال الناقصة فارغة. - super.init قبل تهيئة خصائص الابن: خطأ ترجمة في Swift. هيّئ خصائص الكلاس الوارث أولاً.
- نسيان override: إعادة كتابة دالة الأب بدون
overrideخطأ ترجمة، وليست اختيارية. - خصائص مخزنة في extension: الإضافات تقبل الدوال والخصائص المحسوبة فقط، ولا تقبل خصائص مخزنة جديدة.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
ما الميزة الأساسية للبروتوكول مقارنة بالوراثة في Swift؟
الوراثة للكلاسات فقط ومن أب واحد، أما البروتوكول فمتاح لكل الأنواع وبأي عدد.
سؤال
ماذا تُرجع person as? Manager إذا لم يكن الكائن من النوع Manager؟
as? تحويل آمن يُرجع قيمة اختيارية: الكائن بنوعه الجديد أو nil.
الخلاصة
Section titled “الخلاصة”super.initوsuper.method()للبناء على الكلاس الأب، وfinalتمنع الوراثة.protocolقائمة متطلبات تطابقها الهياكل والكلاسات، وبأي عدد.extensionتضيف دوالاً لأي نوع، وعلى البروتوكول تمنح تنفيذاً افتراضياً.EquatableوComparableوCustomStringConvertibleبروتوكولات جاهزة مفيدة.isتفحص النوع، وas?تحوّل بأمان. ابدأ دائماً بالبروتوكول.