بسم الله الرحمن الرحیم

شاید خیلی وقتا اسم شی گرایی رو شنیده باشید اما این مفهوم جدیدی که امروز میخوایم بررسیش کنیم رو شاید اولین باره که اینجا دارید میشنوید Protocol OP یا اگه بخوایم یه اسم فارسی انگلیسی براش بزاریم باید بگیم پروتکل گرایی.

برنامه نویسی پروتکل گرا یا Protocol OP چی هست؟

اول باید مفهوم خود پروتکل رو توضیح بدم:

اگه توی جاوا تا حالا کد زده باشید یه مفهومی تحت عنوان اینترفیس داشتید و این معادل پروتکل توی سویفت هست حالا اونایی که جاوا کار نکردن این بخش رو بخونن

پروتکل چیست؟

فرض کنید شما نیاز دارید چند تا شکل رو رسم کنید و مساحت و محیطشون رو محاسبه کنید مثلا فرض کنید قراره یه برنامه بسازید که مساحت و محیط دایره و مربع و مثلث و مستطیل رو محاسبه کنه و برای هرکدومشون شکلشو رسم کنه.

میدونید که اینا همه شکل هستن و شما یه سری تابع هم اسم و هم ارگومان رو دارید و وقتی میخواید برای دونه دونه شکل ها کلاس بسازید با اینکه به طور عقلانی بین این شکل ها رابطه ای هست (یعنی همشون شکل هستن و مساحت و محیط دارن) اما در برنامه نویسی هیچ ارتباطی نمیتونید بهشون بدید مگر اینکه از پروتکل یا اینترفیس استفاده کنید

خب ۴ تا کلاس Circle Triangle Rectangle Square باید بسازید و توی هرکدومش توابعی مثل draw calcArea calcPerimeter رو باید بنویسید و محاسبه مساحت و محیط رو انجام بدید.

اما میشه یه کار خیلی تمیزتری کرد که بعدا هم تازه بیشتر میفهمید چقدر جاهای مهمتر دیگه ای کاربرد داره اونم استفاده از اینترفیس هست

اینترفیس یا پروتکل چیه؟

شما قطعا تا الان یه کلاس ساختید و توی کلاستون یه سری تابع یا متغییر تعریف کردید و میدونید که باید توابعتون حتما {} داشته باشه یعنی باید حتما بدنه داشته باشه اما توی اینترفیس فقط اسم توابع و مقدار ورودی هاش تعریف میشه و کلاس های دیگه وقتی اونو به قولی impelement میکنن باید حتما اون توابع رو دقیقا با همون اسم داشته باشن (الزام هست)

پس با این توصیفات شما میتونید یه اینترفیس به نام Shape بسازید که فقط کارش اینه که همون توابع (draw و calcArea و calcPerimeter) رو توش داره و هرکسی هم بخواد اونو implement کنه مجبور میشه که اون توابع رو دقیقا با همون اسم داشته باشه

protocol Shape {
    func draw()
    func calcArea()
    func calcPerimeter()
}

حالا باید مثلا کلاس Square شما این شکلی باشه:

class Square : Shape {
  
    ....


    func draw (){
        print("draw Squre")
    }
    func clacArea(){
        print("Calc Area")
    }
    func calcPerimeter (){
        print("Calc Perimeter");
    }
}

بازم توجه کنید که ما مجبوریم وقتی یه Protocol رو به قولی implement میکنیم همه توابعی که توی اون پروتکل تعریف شده رو باید بنویسیم و براش بدنه بزاریم

چرا نیاز داریم از پروتکل استفاده کنیم؟

همونجوری که گفتم یا می دونستید توی سویفت. یک کلاس فقط میتونه از یه کلاس دیگه ارث بری کنه یعنی نمیتونه از بیشتر از یه کلاس ارث بری کنه ولی میتونه از چندین تا پروتکل فیض ببره.

توی سویفت پروتکل ها قدرت نسبتا خوبی دارن.

به این مثال توجه کنید

در کد زیر یه پروتکل برای اسلحه ساختیم:

protocol Weapon {
    var name: String { get }
    var canFire: Bool { get }
    var canCut: Bool { get }
}
// You can create blueprints for vars and funcs. Creating a var in a // protocol lets your classes to conform that protocol with forcing // to define that variables. In this example, we force developer to // add name, canFire and canCut vars to weapon he/she created using // Weapon protocol

حالا اسلحه ها میتونن چند نوع باشن مثلا بعضی ها میتونن آتش هم داشته باشن بعضیاشونم میتونن چیزی رو بشکافن یعنی یه تیغی چیزی داشته باشن برای این کار بعضیاشونم میتونن هردو رو داشته باشن یا نداشته باشن (۴ نوع شد)

خب پس برای اونایی که Firable یا امکان آتش زدن هم دارن باید یه پروتکل بسازیم:

protocol Fireable {
    var magazineSize: Int { get }
}
protocol Cuttable {
    var weight: Double { get }
    var steel: String { get }
}

 

حالا میریم که دو تا استراکت به نام دوتا اسلحه بسازیم:

 

struct LongSword: Weapon, Cuttable {
    let name: String
    let steel: String
    let weight = 7.2
    let canFire = false
    let canCut = true
}
// longsword is a weapon with ability to cut.
struct AK47: Weapon, Fireable {
    let name = "AK47"
    let magazineSize = 30
    let canFire = true
    let canCut = false
}
// ak47 is also a weapon with ability to fire bullets.

 

خب حالا چیزی که هست و احتمالا بهش توجه کردید اینه که باید به صورت دستی دو تا متغییر canCut و canFire رو مقدار بدید یعنی هم از Firable استفاده میکنید و هم باید دستی بهش بگید که canFire برابر true هست و از این حرفا

سویفت میتونه اینکارو براتون هندل کنه تا وقتی struct شما از firable ایمپلمنت بشه خودش مقدار canFire رو برابر true  برگردونه حالا به چه شکلی؟ با استفاده از extention که در کاتلین و برخی از زبان های دیگه هم هست.

extension Weapon {
    var canFire: Bool { return self is Fireable }
    var canCut: Bool { return self is Cuttable }
}

 

حالا میتونید همه اون canCut ها رو که جاهای دیگه نوشتید پاک کنید

و اگه توی playGround کد زیر رو اجرا کنید نتایج روبه روش (که کامنت شده) رو میبینید.

let longclaw = LongSword(name: "Longclaw", steel: "Valyrian")
longclaw.canCut // true
longclaw.canFire // false
longclaw.name
let ak47 = AK47()
ak47.canFire  // true 
ak47.canCut // false

 

آیا میشه یه پروتکل رو از یه پروتکل دیگه ارث بری کرد؟

فرض کنید یه پروتکل میخواید که علاوه بر داشتن متغییر و توابع یه پروتکل دیگه خودشم یه سری توابع اضافه داشته باشه

مثلا امکان بمب افکن بودن اسلحه:

protocol Bombable: Fireable {
    var maxDistance: Double { get }
}

و حالا یه استراکت جدید:

struct Howitzer: Weapon, Bombable {
    let name = "Howitzer"
    let magazineSize = 1
    let maxDistance = 1000 // in km
}

 

دیگه چیز نمونده پس متوجه قدرت نسبی protocol ها توی سویفت شدید که میتونن برنامتون رو به شکل تمیزی تشکل بدن

بعدا در مورد Delegate هایی که توی آی او اس استفاده میکنیم صحبت میکنم

موفق باشید

یاعلی