آموزش برنامه نویسی سوئیفت: Struct، کلاس، مشخصات و متدها — بخش ششم

۱۱۷ بازدید
آخرین به‌روزرسانی: ۰۹ مهر ۱۴۰۲
زمان مطالعه: ۹ دقیقه
آموزش برنامه نویسی سوئیفت: Struct، کلاس، مشخصات و متدها — بخش ششم

در بخش قبلی از این سری مطالب آموزشی به بررسی برخی از مفاهیم ابتدایی زبان برنامه‌نویسی سوئیفت پرداختیم. با مطالعه بخش قبلی مقاله، درک خوبی از شیوه تقسیم کردن کد به تابع‌های مختلف به دست آوردیم. همچنین با شیوه ایجاد گزینه‌های متفاوت با استفاده از enum-ها و اصلاح گزاره‌های if/else واقعاً طولانی و پایین نگه‌داشتن مصرف حافظه و رفع برخی باگ‌های رایج با تعریف صحیح دامنه‌ها نیز آشنا شدیم. در این بخش از سری مطالب آموزش سوئیفت قصد داریم در مورد دو شیء جدید که نوع داده هستند و به عنوان کانتینرهایی به مجزا نگه‌داشتن کد و خوانایی هر چه بیشتر آن کمک می‌کنند صحبت کنیم. البته باید توجه داشته باشید، چنین نیست که هر چه کد به اجزای بیشتری تقسیم شود، خوانایی آن افزایش می‌یابد؛ بلکه یک نقطه تعادل وجود دارد. کلاس‌ها و struct-ها به ساختن این نقطه تعادل کمک می‌کنند. بنابراین توضیح خود را از مفهوم struct آغاز می‌کنیم.

Struct

Struct (مانند ساختمان‌ها در C) نوع داده‌ای شامل متغیرهای مشابه است. در سوئیفت این مفهوم بسط یافته است تا شامل تابع‌ها، enum-ها و دیگر struct-ها نیز باشد.

آن‌ها را به روش زیر اعلان می‌کنیم:

1struct House {
2    let squareFootage: Double
3    let numberOfRooms: Int
4    let numberOfWindows: Int
5    let numberOfBathrooms: Float
6    let address: String
7  
8    var doorIsOpen: Bool
9  
10    struct Garage {
11        let numberOfCars: Int
12        let numberOfWindows: Int?
13        var doorIsOpen: Bool
14    }
15}

در کد فوق یک struct را با استفاده از struct House اعلان می‌کنیم و سپس در بدنه آن هر چند عدد متغیر، ثابت یا دیگر struct-ها را که مناسب باشد قرار می‌دهیم. اگر لازم باشد که مقدار متغیری در آینده تغییر یابد آن را با استفاده از var اعلان می‌کنیم و اگر چنین نباشد برای اعلان ثابت‌ها از let استفاده می‌کنیم.

هنگام نوشتن متغیرها برای نام‌گذاری، توجه می‌کنیم که این متغیر برای اندازه‌گیری یا تعیین چه چیزی مورد استفاده قرار می‌گیرد و سپس با کلیدواژه let نام متغیر را اعلان می‌کنیم. در ادامه به بررسی نوع داده‌هایی که متغیر نگه‌داری خواهد کرد می‌پردازیم. سؤالات زیر به منظور این بررسی مناسب هستند:

  • آیا استفاده از اعداد اعشاری (Double/Float) معنی دارد؟
  • آیا استفاده از اعداد کامل (Int) مناسب است؟
  • آیا متغیر به تعیین یک وضعیت (True/False) خواهد پرداخت؟
  • آیا متغیر می‌تواند چندین معیار (Struct) را اندازه‌گیری یا تعیین کند؟

تعیین نوع برای متغیر

پاسخ به چنین سؤالاتی موجب می‌شود که راحت‌تر بتوانیم در مورد نوع متغیرهایی که می‌خواهیم مورد استفاده قرار دهیم تصمیم‌گیری کنیم. squareFootage را می‌توان به وسیله اعداد صحیح اندازه‌گیری کرد؛ اما می‌تواند شامل اعداد اعشاری نیز باشد بنابراین نوع آن را به صورت Double تعریف می‌کنیم. ما هرگز نمی‌توانیم نصف اتاق یا یک ربع اتاق داشته باشیم و از این رو numberOfRooms به صورت Int خواهد بود. همین موضوع در مورد numberOfWindows نیز صادق است؛ اما متغیر numberOfBathrooms به صورت Float اعلان شده است. بدیهی است که ما هرگز نمی‌توانیم یک حمام نصفه داشته باشیم؛ اما تصور کنید در حمامی که دو وان قرار دارند، اگر تنها اجازه استفاده از یکی از آن‌ها را داشته باشیم، در واقع نیمی از حمام در اختیار ما است و بدین ترتیب حمام نصفه معنی پیدا می‌کند.

نوع String

Address نیز به صورت رشته اعلان شده است و البته یک ثابت است. دلیل ثابت بودن این مقدار آن است که دلیلی برای تغییر یافتن نشانی یک فرد وجود ندارد و تنها دلیل آن نقل مکان از یک خانه به خانه دیگر می‌تواند باشد. در این حالت آن خانه دیگر به فرد تعلق ندارد و از این رو بهتر است آن را به صورت پیش‌فرض به عنوان یک ثابت تعریف کنیم. در نهایت var doorIsOpen را داریم که می‌تواند دو مقدار درست/نادرست را بگیرد و یک متغیر بولی است.

نوع Int

البته می‌توانیم یک مقدار Garage نیز به این struct اضافه کنیم و از آنجا که می‌دانیم که یک گاراژ می‌تواند خودروهای زیادی در خود جای دهد، در ادامه متغیر numberOfCars را اعلان کرده و نوع آن را Int تعیین می‌کنیم. گاراژ ما می‌تواند پنجره داشته باشد و از این رو می‌توانیم با استفاده از ?numberOfWindows: Int امکان تعیین پنجره را داشته باشیم. در نهایت باید بدانیم که آیا در باز یا بسته است تا متوجه شویم که آیا می‌توانیم ماشین را از گاراژ خارج کنیم یا نه. این کار با استفاده از متغیر doorIsOpen صورت می‌گیرد.

مثال عملی دامنه متغیرها در Struct

ما قبلاً متغیری به نام doorIsOpen داشتیم و این بخشی از Struct ما به نام House بوده است. اما به دلیل بحث «دامنه» (Scope) می‌دانیم که متغیر doorIsOpen درون struct گاراژ، آن متغیر doorIsOpen که درون Struct خانه اعلان شده نیست. در ادامه روش استفاده از این struct برای ایجاد یک خانه را می‌بینید:

1struct House {
2    let squareFootage: Double
3    let numberOfRooms: Int
4    let numberOfWindows: Int
5    let numberOfBathrooms: Float
6    let address: String
7  
8    var doorIsOpen: Bool
9  
10    struct Garage {
11        let numberOfCars: Int
12        let numberOfWindows: Int?
13        var doorIsOpen: Bool
14    }
15}
16
17var myHouse = House(squareFootage: 1250,
18                    numberOfRooms: 3,
19                    numberOfWindows = 9,
20                    numberOfBathrooms = 1.5,
21                    address: "123 Elm Street"
22                    doorIsOpen = false
23                    garage(numberOfCars = 0,
24                           numberOfWindows = nil,
25                           doorIsOpen = false))
26
27// The only thing we can change is whether the door is open or 
28// closed
29myHouse.doorIsOpen = true
30myHouse.doorIsOpen = false
31
32print(myHouse.squareFootage)       //prints 1250.0
33print(myHouse.Garage.numberOfCars) //prints 0

باید توضیح دهیم که در کد فوق از عملگر نقطه (.) برای دریافت عناصر مختلف myHouse استفاده کرده‌ایم. اگر لازم باشد یک متغیر را از struct گاراژ بخوانیم، می‌توانیم از طریق struct خانه به آن دسترسی داشته باشیم. روش کار به این صورت است:

1myHouse.Garage.doorIsOpen

قبلاً اشاره کردیم که متغیر doorIsOpen بین دو struct خانه و گاراژ متفاوت است و دلیل این جداسازی آن است که myHouse.doorIsOpen همان myHouse.Garage.doorIsOpen نیست.

تعریف غیر تو در تو برای Struct

اگر مطمئن نیستید که خانه شما گاراژ خواهد داشت یا نه، می‌توانید این دو struct را از حالت تو در تو خارج کرده و آن‌ها را به دو struct مجزا تقسیم کنیم. در این حالت خانه می‌تواند گاراژ داشته باشد یا نداشته باشد.

1struct House {
2    let squareFootage: Double
3    let numberOfRooms: Int
4    let numberOfWindows: Int
5    let numberOfBathrooms: Float
6    let address: String
7  
8    var doorIsOpen: Bool
9  
10    var garage: Garage?
11}
12
13struct Garage {
14    let numberOfCars: Int
15    let numberOfWindows: Int?
16  
17    var doorIsOpen: Bool
18}
19
20let garage = Garage(numberOfCars = 2,
21                    numberOfWindows = 1,
22                    doorIsOpen = true)
23
24let myHouse = myHouse(squareFootage: 2000,
25                    numberOfRooms: 4,
26                    numberOfWindows: 16,
27                    numberOfBathrooms: 2,
28                    address: "456 Oak Dr.",
29                    doorIsOpen: false,
30                    garage: garage)
31
32print(myHouse.garage.numberOfCars) // prints 2

در کد فوق ما Garage را به یک Struct مستقل تبدیل کرده‌ایم. در مواردی که خانه‌ای دارای گاراژ باشد، می‌توانیم متغیری به نام garage در Struct خانه داشته باشیم که به صورت Optional یعنی ?Garage تعریف می‌شود.

دقت کنید که اگر می‌خواهید یک چنین وضعیتی را داشته باشید، باید ترتیب کدها را حفظ کنید. اگر می‌خواهید myHouse را با یک garage مقداردهی کنید، باید ابتدا یک garage بسازید. همچنین از آنجا که garage اختیاری است، می‌توانیم آن را به صورت تهی (nil) تعیین کنیم و در ادامه یک newGarage ایجاد کرده و آن را به متغیر myHouse به صورت زیر انتساب دهیم:

1myHouse.garage = newGarage

Struct

کلاس‌ها

کلاس‌ها در ظاهر عملکردی دقیقاً همانند struct دارند. در واقع تفاوت آن دو آن قدر در سطوح عمیقی قرار دارد که اغلب افراد درک نمی‌کنند چرا باید یکی را بر دیگری ترجیح دهند. البته اگر از اغلب افراد سؤال کنید خواهند گفت که تفاوت این دو کاملاً ساده است: کلاس‌ها «انواع ارجاعی» (reference type) هستند و Struct-ها «انواع مقداری» (value type) محسوب می‌شوند.

گرچه شاید این تفاوت ساده به نظر بیاید؛ اما در واقع بسیار پیچیده است. کلاس‌ها به وسیله ارجاع ارسال می‌شوند. Struct-ها بر اساس مقدار ارسال می‌شوند. در مثال زیر این تفاوت را به صورت عملی ملاحظه می‌کنید:

1// Using a Class
2class House {
3    var doorIsOpen: Bool
4}
5
6var myHouse = House(doorIsOpen: true)
7var yourHouse = House(doorIsOpen: false)
8
9print(myHouse.doorIsOpen)    //prints true
10// assign yourHouse equal to myHouse
11yourHouse = myHouse
12
13// Close the door on your house
14yourHouse.doorIsOpen = false
15
16// Recheck the value of myHouse's door
17print(myHouse.doorIsOpen)    // prints false
18// Using a struct
19struct HouseStruct {
20    var doorIsOpen: Bool
21}
22
23var myHouseStruct = HouseStruct(doorIsOpen: true)
24var yourHouseStruct = HouseStruct(doorIsOpen: false)
25
26print(myHouse.doorIsOpen)     // prints true
27yourHouseStruct = myHouseStruct
28yourHouseStruct.doorIsOpen = false
29
30print(myHouseStruct.doorIsOpen)   // prints true

تفاوت ارسال با ارجاع یا مقدار

زمانی که از myHouse به عنوان یک کلاس استفاده می‌کنیم چه اتفاقی می‌افتد؟ زمانی که وهله‌ای از myHouse می‌سازیم یک آدرس در حافظه به عنوان ارجاع به House خودمان، برای آن تخصیص می‌دهیم. این آدرس به همه اطلاعات مورد نیاز برای آن وهله از House اشاره می‌کند. زمانی که yourHouse را ایجاد می‌کنیم یک آدرس متفاوت در حافظه به آن می‌دهیم که شامل همه اطلاعات خانه شما است.

اینک نوبت به بخش پیچیده‌تر می‌رسد. زمانی که انتساب yourHouse = myHouse صورت می‌گیرد، قصد نداریم بگوییم که خانه شما شبیه خانه ما است؛ بلکه صرفاً بیان می‌کنیم که yourHouse همان myHouse است و به این ترتیب آدرس حافظه آن را به همان آدرس حافظه myHouse تعیین می‌کنیم. بنابراین هنگامی که در مربوط به خانه yourHouse را می‌بندیم در واقع در مربوط به خانه myHouse را بسته‌ایم. اطلاعات موجود در حافظه برای yourHouse کمی پس از انتساب yourHouse به myHouse حذف می‌شوند.

در ادامه دقیقاً همین کار را با استفاده از struct-ها اجرا کرده‌ایم. در این روش ما مقدار را در متغیر ذخیره می‌کنیم. زمانی که انتساب yourHouseStruct = myHouseStruct انجام بپذیرد، کاری که انجام می‌شود این است که مقدار یک متغیر از عددی به عدد دیگر تغییر پیدا می‌کند. Struct-ها به صورت copy-on-write هستند. یعنی هنگامی که انتساب yourHouseStruct = myHouseStruct صورت می‌گیرد، سیستم در عمل مکانی از حافظه که اطلاعات myHouseStruct در آن قرار دارند را مورد ارجاع قرار می‌دهد. آن را می‌توان مانند حالت زیر تصور کرد:

1yourHouseStruct = myHouseStruct = myHouseStructInfo

به محض این که در مربوط به خانه yourHouseStruct بسته شود، یک کپی از myHouseStruct در جای دیگری از حافظه ایجاد می‌شود و yourHouseStruct به مکان جدیدی از حافظه ارجاع می‌یابد.

ارث‌بری در کلاس‌ها

یک کلاس می‌تواند از کلاس دیگری ارث‌بری کند. این گفته بدان معنی است که هر کلاسی که از کلاس دیگر ارث‌بری کند، متغیرها و توابع خاص خود را دارد؛ اما متغیرها و توابع کلاس والد یا سوپرکلاس خود را نیز که از آن به ارث رسیده است، شامل می‌شود. شیوه کار به صورت زیر است:

1class Ball {
2    let size: Int
3    let bounciness: Int
4    let color: String
5}
6
7class SoccerBall: Ball {
8    let secondaryColor: String
9}
10
11class BaseBall: Ball {
12    let threadColor: String
13}

در کد فوق یک کلاس به نام Ball داریم. این کلاس متغیرهای size، bounciness و color را دارد. SoccerBall با استفاده از ساختار SoccerBall: Ball ارث‌بری کرده است. این بدان معنی است که وهله‌ای از SoccerBall می‌تواند نه تنها شامل secondaryColor بلکه شامل size، bounciness و color نیز باشد. از آنجا که Baseball همان SoccerBall نیست، احتمالاً نمی‌خواهیم Baseball از SoccerBall ارث‌بری کند؛ اما آن نیز یک نوع توپ یا Ball است و از این رو می‌تواند از Ball ارث‌بری کرده تا متغیرهای موجود در آن را دریافت کند. تنها تفاوت در این است که ما در این وضعیت threadColor را داریم که مستقیماً در کلاس BaseBall تعریف شده است.

روش دیگری که می‌توان برای توضیح مفهوم ارث‌بری در کلاس‌ها استفاده کرد، تشبیه به علم زیست‌شناسی است که در آن ارگانیسم‌ها به فرمانرو، شاخه، رده، راسته، خانواده، سرده و گونه تقسیم می‌شوند. هر آن چه در مورد مرتبه‌های بالاتر یک طبقه‌بندی از حیوانات صدق کند در مورد مرتبه‌های پایین‌تر نیز صدق می‌کند؛ اما مواردی که صرفاً در مرتبه‌های پایین وجود دارند، ممکن است در مرتبه‌های بالاتر این رده‌بندی جایی نداشته باشند.

متدها و مشخصات

متد در واقع نام دیگری برای تابع است که هنگام استفاده در کلاس یا Struct به آن تعلق می‌گیرد. نام کامل آن «متد وهله‌ای» (instance method) است، زیرا به وهله‌ای از یک کلاس یا Struct تعلق دارد، اما ما آن‌ها را به اختصار متد می‌نامیم. در بخش‌های قبلی این سری مطالب آموزش سوئیفت با برخی کاربردهای عملی متدها آشنا شدیم. هنگامی که از ساختار var arrayCount = someArray.count استفاده کردیم؛ نوع آرایه یک متد داخلی دارد که برای استنباط نوع arrayCount به صورت نوع «صحیح» (Integer) استفاده می‌شود.

«مشخصات» (Properties) نیز در واقع نام دیگری برای متغیرها یا ثابت‌های متعلق به یک وهله از کلاس یا Struct هستند. ما در این نوشته مشخصاتی برای House، یا Ball و حتی SoccerBall تعریف کردیم. به مثال زیر توجه کنید:

1class House {
2    var isDoorOpen: Bool
3    
4    func toggleDoor() {
5        self.isDoorOpen = !isDoorOpen
6    }
7}
8
9var myHouse = House(isDoorOpen: true)
10
11myHouse.toggleDoor()    // shuts the door
12myHouse.toggleDoor()    // opens the door

در کد فوق ما به یک متد myHouse دسترسی پیدا می‌کنیم. با استفاده از عملگر نقطه و فراخوانی تابع ()toggleDoor می‌توانیم بسته به این که حالت کنونی در باز یا بسته است، آن را باز یا بسته کنیم. ما باید قبل از باز یا بسته کردن در با استفاده از کد زیر، وضعیت آن را بررسی کنیم تا در این خصوص مطمئن شویم:

1if myHouse.isDoorOpen { myHouse.toggleDoor() }

کد فوق تنها در صورتی در را می‌بندد که باز باشد.

در آخرین بخش از این راهنما از کلیدواژه Self استفاده می‌کنیم. Self به وهله‌ای از این شیء اشاره دارد. زمانی که از self.isDoorOpen استفاده می‌کنیم، در واقع به myHouse.isDoorOpen اشاره داریم؛ اما از این حالت استفاده می‌کنیم، زیرا در نقطه‌ای که از آن استفاده می‌کنیم، ایده دقیقی در دست نیست که نام متغیر چه خواهد بود. این متغیر می‌تواند yourHouse یا theirHouse باشد. با استفاده از Self می‌توانیم بدون نگرانی در مورد این که ساختار صحیحی استفاده شده یا نه؛ امکان دسترسی به این کارکرد را داشته باشیم.

سخن پایانی

در این نوشته در مورد Struct و کلاس در زبان سوئیفت نکاتی را آموختیم. با شباهت‌ها و تفاوت‌های آن‌ها آشنا شدیم. البته نکات بسیار زیادی دیگری وجود دارد که باید در این خصوص آموخت؛ اما باید بدانید که در سوئیفت ما به صورت پیش‌فرض از Struct استفاده می‌کنیم و تنها در مواردی که کارکردهای مورد نیاز خود را نیابیم از کلاس استفاده می‌کنیم. در این موارد کافی است در ابتدای تعریف به جای struct از class استفاده کنید. همچنین در این راهنما آموختیم که به متغیر درون struct یا class، مشخصه گفته می‌شود و تابع درون آن‌ها نیز، متد نام دارد. در بخش بعدی در مورد کلاس‌ها و Struct-ها بیشتر صحبت می‌کنیم و در خصوص «مقداردهی» (Initialization) و همچنین De-initialization ،Override و «شمارش ارجاع» (Reference Counting) نکاتی یاد خواهید گرفت.

برای مشاهده قسمت بعدی این مطلب روی لینک زیر کلیک کنید:

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۱ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
swift2go
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *