سلام من برگشتم! خب امروز میخوام راجع به یه مفهوم صحبت کنم به نام Operator Overloading. و از اونجایی که خستمه (?) و برام سخته هر بار بخوام کل کلمه ش رو تایپ کنم از این به بعد میگم oo. ایده این مفهوم اینه که شما میتونید رفتار یک عملگر مثل == رو تغییر بدید. بذارین با یه مثال ساده شروع کنیم:
فرض کنید یک کلاس فرد (Person) داریم که شامل کد ملی و نام فرد هست:
class Person { var name: String var nationalCode: String init(name: String, nationalCode: String) { self.name = name self.nationalCode = nationalCode } }
خب حالا فرض کنید من میخوام عملگر == رو تعریف کنم تا بتونم دوتا شی از کلاس Person رو با هم مقایسه کنم. این کارو اینطوری انجام میدم:
extension Person { static func == (left: Person, right: Person) -> Bool { return left.name == right.name && left.nationalCode == right.nationalCode } }
حالا میتونم از این عملگر واسه اشیا کلاسم استفاده کنم:
let person = Person(name: "Pouya", nationalCode: "001") let samePerson = person let anotherPerson = Person(name: "Payam", nationalCode: "002") print(person == samePerson) print(person == anotherPerson) // OUTPUT: // true // false
حالا میخوام یه مثال دیگه براتون بزنم تا بیشتر با مفاهیم مرتبط با oo آشنا بشید. همونطور که احتمالا میدونید توی سوییفت عملگر ++ نداریم و من میخوام الان بسازمش. برای اینکار لازمه بدونید که عملگرها میتونن سه حالت داشته باشن:
عملگر ++ وقتی به صورت پیشوندی باشه اول مقدار متغیر رو زیاد میکنه و بعد مقدارشو برمیگردونه. و وقتی به شکل پسوندی استفاده بشه اول مقدار برمیگرده و بعد زیاد میشه. خب بریم کدش رو با هم ببینیم تا بعدش توضیح بدم چی شده
prefix func ++ <T: Numeric> (right: inout T) -> T { right += 1 return right } postfix func ++ <T: Numeric> (left: inout T) -> T { let value = left left += 1 return value } var counter = 0 counter++ ++counter
خب همونطور که میبینید من اول اومدم این Overload رو به ازای تمام مقادیر عددی تعریف کردم (یعنی اومدم گفتم جنس عملوند من T هست که بصورت Generic تعریف شده و بعد گفتم که Numeric هست). بعد اومدم گفتم خروجی عملگرمم از همون جنس عملوندم هست. اینم بگم که عملوندم رو به صورت inout تعریف کردم تا امکان تغییر مقدارش رو داشته باشم. پس اینم از مثال عملگرهای پسوندی و پیشوندی. بریم سراغ مثال آخر.
توی این مثال میخوام یه کلاس تعریف کنم که بیان کننده گره های لینک لیست هست. هر گره یه دیتا داره که من رشته در نظر گرفتم و یه ارجاع به گره بعدی خودش که منطقا Optional هست چون آخرین گره لیست پیوندی به گره دیگه ای اشاره نمیکنه اگر لیستمون حلقوی نباشه.
کلاس گره رو اینطوری تعریف میکنم:
class Node { var data: String weak var link: Node? init(_ data: String) { self.data = data } }
خب حالا فرض کنید میخوام یه عملگر تعریف کنم که یه گره رو به یه گره دیگه لینک کنه یعنی مثلا
A ~> B
رفتارش اینطوری باشه که گره A به گره B به عنوان گره بعدیش اشاره کنه. برای این کار عملگرم رو اینطوری تعریف میکنم:
func ~> (left: Node, right: Node) { left.link = right }
خب حالا چندتا گره تعریف میکنم:
let node1 = Node("BMW") let node2 = Node("Audi") let node3 = Node("Volvo") let node4 = Node("Nissan") let node5 = Node("Honda")
حالا برای لینک کردنشون به هم باید بگم:
node1 ~> node2 node2 ~> node3 node3 ~> node4 ...
موافقین راه حل خوبی نیست و کدنویسی زیادی داره؟ بهتر نمیشد اگه میتونستم تو یه خط چند بار عملگری که تعریف کردم به کار ببرم؟
خب باید بگم که برای عملگرهای میانوندی مفهومی هست به نام precedencegroup که کمک میکنه مجموعه ای از عملگرا با هم به کار برن. الان یه precedencegroup تعریف میکنم تا بتونم چند بار عملگرم رو کنار هم استفاده کنم و اولویت اعمال عملگر رو از هم چپ به راست در نظر میگیرم.
precedencegroup LinkerOperator { associativity: left }
خب حالا عملگرم رو به عنوان عضو این precedencegroup تعریف میکنم:
infix operator ~> : LinkerOperator
تا یادم نرفته تابع Overload که بالا نوشته بودم رو هم یه تغییر کوچیک بدم:
func ~> (left: Node, right: Node) -> Node { left.link = right return right }
تنها تغییری که دادم این بود که آخرش که لینک کردم عملوند سمت راست رو برای عملگرای بعدی دوباره return کردم. همین :)
تنها کاری که میخوام بکنم اینه که یه تابع بنویسم که لیستم رو پیمایش کنه تا بتونیم نتیجه رو ببینیم:
func showLinkList(_ linkList: Node) { print(linkList.data) if let link = linkList.link { showLinkList(link) } }
تبریک! حالا دیگه عملگرمون آماده استفاده ست:
let starterNode = node1 node1 ~> node2 ~> node3 ~> node4 ~> node5 showLinkList(starterNode) // OUTPUT: // BMW // Audi // Volvo // Nissan // Honda
ما در مورد مفهوم oo صحبت کردیم و سه تا مثال ازش دیدیم. توی مثال اول بیشتر مفهوم oo رو شناختیم. توی مثال دوم با بررسی انواع عملگر موفق شدیم عملگر ++ رو پیاده سازی کنیم و توی مثال سوم با precedencegroup برای عملگرهای میانوندی آشنا شدیم. امیدوارم این مقاله کمکتون کرده باشه و اگر کمک کننده بوده لایک فراموش نشه لطفا ☺️?