طراحی ساختار برنامه
چه انواع شیئی توی برنامه نیاز داریم؟ چه متدها و خصوصیاتی باید توی این انواع وجود داشته باشن؟ چهموقع و چطور باید ازشون شئ نمونه بسازین و وقتی شئ ساختیم، باهاش چهکارهایی باید انجام بدیم؟ اینها واقعاً تصمیمهای سادهای نیستن و هیچ جواب واضح و قطعی خاصی براشون وجود نداره. «برنامهنویسی شئگرا یه هنره»!
در واقعیت، وقتی که دارین برنامهنویسی iOS انجام میدین، انواع زیادی از اشیاء وجود دارن که شما باهاشون کار میکنین ولی مال شما نیستن و متعقل به Apple هستن. Swift خودش چندین نوع شئ مفید و کاربردی داره مثل String و Int و... و همچنین وقتی دستور
import UIKit رو مینویسین، تعداد «بسیار زیادی» از انواع شئ وارد برنامهی شما میشه که هرکدوم بخشی از برنامهی شما رو زنده میکنن. شما هیچکدوم از این انواع رو نساختین، بنابراین طراحی اونها مشکل شما نیست؛ درعوض باید یاد بگیرین که چطور ازشون استفاده کنین. انواع شئ داخلی Apple طوری طراحی شدن که قابلیتهای «عمومی» رو که هر برنامه ممکنه بهشون نیاز داشته باشه، در دسترس شما بگذارن. در همین زمان، برنامهی شما احتمالاً نیاز به کارآییهای «اختصاصی» خواهد داشت که مختص هدف همون برنامه هستن و باید انواع شئ اختصاصی خودتون رو برای رسیدن به اون هدف طراحی کنین.
طراحی یه برنامهی شئگرا باید برپایهی یه درک عمیق و کامل از «ذات اشیاء» انجام بشه. شما میخواین انواع مختلفی تعریف کنین که مجموعهی صحیحی از قابلیتها (رفتارها یا متدها) و دادهها (خصوصیات یا فیلدها) رو شامل بشن. بعداً وقتی که از این انواع، شئ نمونه میسازین، میخواین که مطمئن بشین اشیاء شما طولعمر صحیح و قابلیت دید مناسب نسبتبه سایر اشیاء داشتن باشن و بتونن با همدیگه ارتباط مناسبی برقرار کنن.
انواع اشیاء و API
فایلهای برنامهی شما در اغلب موارد هیچ متغیر یا تابع سطحبالایی نخواهند داشت یا اگه باشه هم تعدادشون خیلی کمه. متدها و خصوصیات انواع شیئی که تعریف میکنین (فیلدها و متدهای نمونهای) جایی هستن که بیشترین کارها انجام میشه. انواع شئ به هر نمونه قابلیتهای اختصاصی خودشو میدن. همچنین به شما کمک میکنن که کد برنامهی خودتون رو بهشکل معنادار و قابل نگهداری، سازماندهی کنین.
نقل قول:ما «ذات اشیاء» رو در دو عبارت خلاصه میکنیم: «گردآوری قابلیتها» و «نگهداری وضعیت».
- گردآوری قابلیتها: هر شئ کارهای خاص خودشو انجام میده و به بقیهی دنیا (سایر اشیاء و حتی خود برنامهنویس)، یه دیوار محکم و غیرقابل نفوذ نشون میده که تنها نقاط ورود به اون، متدهایی هستن که تعهد دادن به درخواستها در زمانیکه پیامهای مناسب براش ارسال بشن، پاسخ مناسب بدن. جزئیات نحوهی انجام کارها در پشتصحنه، درحقیقت داخل همون شئ مخفی هستن و هیچ شئ دیگری نیاز به آگاهی از اونها نداره.
- نگهداری وضعیت: هر شئ در برنامه، ترکیبی از دادههایی هست که نگهداری میکنه. اغلب اوقات اون دادهها خصوصی هستن و درنتیجه داخل دیوار شئ محافظت میشن. هیچ شئ دیگری اطلاعی درخصوص دادههای داخل شئ و ساختارشون نداره. تنها راه برای کشف اطلاعات محرمانهای که شئ داخل خودش نگهداری میکنه اینه که یه متد یا خصوصیت عمومی وجود داشته باشه که اون رو آشکار کنه.
بعنوان یه مثال، شیئی رو تصور کنید که که وظیفهاش پیادهسازی یه پُشته (Stack) هست (میتونه یه شئ از کلاسی بهاسم Stack باشه که شما ساختین). یه پشته ساختار خاصی برای نگهداری دادههاست که مجموعهی اطلاعات رو با ترتیب LIFE (مخفف Last In, First Out) نگهداری میکنه. این شئ فقط به دو پیام پاسخ میده: push و pop. دستور push یعنی یه داده که دریافت کردیم رو به مجموعه اضافه کنیم و pop هم یعنی آخرین دادهای که به مجموعهی دادههامون اضافه شده بود رو برگردونیم و از مجموعه حذف کنیم. این ساختار دقیقاً شبیه پشتهای از بشقابهاست که روی هم قرار گرفتن: بشقابها یکی پساز دیگری روی هم قرار میگیرن و از همون بالا هم اونها رو برمیداریم. بنابراین برای برداشتن اولین بشقابی که گذاشتیم، باید تمام بشقابهایی که بعداً اضافهشدن رو یکییکی برداریم.
هر شئ نمونه از نوع Stack در این مثال، بهخوبی گردآوری قابلیتها رو نشون میده چون دنیای بیرون هیچچیزی درمورد نحوهی پیادهسازی پشته در نوع Stack نمیدونه. میتونه یه آرایه، یه لیست پیوندی یا هر روش پیادهسازی دیگه باشه. هر بخشی از برنامه که از این شئ استفاده میکنه (مشتریان شئ که پیامهای push و pop رو براش میفرستن) هیچچیزی درمورد این ساختار نمیدونن و اهمیتی هم نمیدن چون خود شئ این کارها رو داره انجام میده و مثل یه پشته عمل میکنه. اینموضوع برای برنامهنویس هم خوبه چون میتونه درحین توسعهی برنامه، با ایمنی کامل یه پیادهسازی رو با یکی دیگه که بنظرش کارآمدتر میاد، عوض کنه بدون اینکه به کل ساختار برنامه و مکانیزم کارکردش آسیبی وارد بشه.
شئ پشتهی ما نگهداری وضعیت رو هم بهخوبی نشون میده چون فقط یه گذرگاه برای دادههای پشته نیست؛ بلکه «خودش» دادههای پشته است. اشیاء دیگه به پشته فقط ازطریق اتصال به شئ دسترسی پیدا میکنن، اونم فقط از کانالهایی که شئ در اختیارشون گذاشته. دادههای پشته بهشکل کارآمدی داخل شئ پشته پنهان و ذخیرهسازی شدن و هیچکسی بجز خود شئ اونها رو نمیبینه. تنها کاری که بقیهی اشیاء میتونن انجام بدن، push یا pop هست. ازطرفی اگه چند شئ پشته داشتیم باشیم، هرکدوم دادههای پشتهی خاص خودشون رو دارن و درنتیجه وضعیت اختصاصی خودشون رو هم خواهند داشت و ارسال پیام push یا pop برای یه شئ پشته، تغییری در وضعیت (دادههای) یه شئ دیگهی پشته ایجاد نمیکنه.
مجموعه تمام پیامهایی که برای هر نوع شئ میتونیم بفرستیم، API (مخفف Application Programming Interface) یا رابط کاربری برنامهنویسی اون نوع نام داره و شبیه فهرست یا منویی از چیزهایی هست که میتونین از این نوع شئ بخواین تا انجام بده. انواع شئ شما کدتون رو تقسیمبندی میکنن. API اونها زیربنای ارتباط بین این تقسیمبندیها رو مشخص میکنن. همین موضوع درمورد اشیائی که شما طراحی نکردین هم وجود داره. مستندات Cocoa شرکت Apple فهرست بزرگی از APIهای اشیاء مختلف رو شامل میشه. برای مثال، اگه میخواین بدونین چه پیامهایی رو میشه برای یه نمونهی NSString بفرستین، کافیه مستندات کلاس NSString رو بخونید. توی اون صفحه، فهرست واقعاً بزرگی از خصوصیات و متدها وجود داره و درنتیجه به شما میگه که یه شئ NSString چه کارهایی میتونه انجام بده و بنابراین، به شما تمام اطلاعاتی که درخصوص نحوهی استفاده از NSString توی برنامهی خودتون لازم دارین رو میده.
ایجاد شئ، محدودهی دید و طولعمر
موجودیتهایی در برنامه که در هر لحظه توی یه برنامهی سویفت وجود دارن، اغلبشون اشیاء نمونه هستن. انواع شئ مشخص میکنن که چه «نوع» اشیائی میتونن وجود داشته باشن و هر نوع چطور رفتار میکنه. اما درحقیقت، این نمونههای واقعی اشیاء هستن که «چیزها»ی مختلفی رو تشکیل میدن که برنامه رو بهوجود میارن و متدها و خصوصیات نمونهای، پیامهایی هستن که میتونیم برای نمونهها بفرستیم. بنابراین لازمه که نمونهها «وجود داشته باشن» تا برنامه بتونه «کار کنه».
هرچند بطور پیشفرض، هیچ شیئی وجود نداره. توی این مثال ما چند نوع شئ تعریف کردیم ولی هیچ نمونهای ازشون نساختیم:
import UIKit
var one = 1
func changeOne() {
let two = 2
func sayTwo() {
print(two)
}
class MyClass {}
struct MyStruct {}
enum MyEnum {}
one = two
}
class Student {
var name = "ali"
func sayName() {
print(name)
}
class MyClass {}
struct MyStruct {}
enum MyEnum {}
}
struct Person {
var name = "mina"
func sayName() {
print(name)
}
class MyClass {}
struct MyStruct {}
enum MyEnum {}
}
enum WeekDay {
let days : String {
return "Friday"
}
func sayName() {
print(name)
}
class MyClass {}
struct MyStruct {}
enum MyEnum {}
}
اگه این برنامه رو اجرا کنیم، انواع شیئی که ساختیم از لحظهی اجرا وجود خواهند داشت ولی این تنها چیزیه که وجود داره. ما دنیایی از امکانات بالقوه ساختیم: انواعی از اشیاء که «میتونن وجود داشته باشن». در چنین دنیایی، هیچچیزی واقعاً اتفاق نمیفته.
نمونهها با جادوگری و شعبهبازی بهوجود نمیاد. شما باید از یه نوع، نمونهسازی کنین تا یه نمونه بدست بیارین. بنابراین بخش زیادی از کار شما در برنامه، شامل ایجاد اشیاء نمونه از انواع مختلف هست؛ و البته شما میخواین که اون نمونهها باقی بمونن (تا بتونین باهاشون کار کنین). بنابراین هر نمونهی جدیدی که میسازین رو توی جعبهای بهاسم متغیر ذخیره میکنین و بهش اسم میدین تا نگهش داره و اون متغیر یه طولعمر داره. نمونهی ایجادشده تازمانی باقی میمونه که متغیری که اون رو نگهداری میکنه، زنده باشه. همچنین این نمونه براساس محدودهی اعتبار و دید (Scope) متغیری که اون رو نگهداشته، توسط سایر اشیاء و قسمتهای برنامه قابل مشاهده خواهد بود.
بیشترین هنر شما در برنامهنویسی شئگرا مربوطبه دادن طولعمر و محدودهی دید مناسب به متغیرهایی هست که نمونهها رو داخل خودشون نگهداری میکنن. در اغلب اوقات یه نمونه رو توی یه جعبه میگذارین (اون رو به یه متغیر نسبت میدین) که این متغیر توی یه محدودهی دید مشخص تعریف شده و بهلطف قوانین مربوطبه طولعمر و محدودهی دید متغیرها، نمونهی ایجادشده بهحد کافی (یعنی تا زمانیکه لازمه توی برنامهی شما قابلاستفاده باشه بهش نیاز دارین) باقی میمونه و سایر قسمتهای کد میتونن یه «ارجاع» به اون نمونه داشته باشن و براش پیام بفرستن.
طرحریزی اینکه چطور میخواین نمونهها رو بسازین و با طولعمر و نحوهی ارتباطشون با سایر نمونهها کار کنین، ممکنه یکم شما رو بترسونه. خوشبختانه در دنیای واقعی، وقتی که دارین یه برنامهی iOS مینویسین، فریمورک Cocoa یکسری ساختارهای اسکلتبندی پروژه رو از ابتدا براتون فراهم میکنه. قبلاز اینکه حتی یکخط کد بنویسید، خود فریمورک تضمین میکنه که برنامهی شما وقتی اجرا بشه، یکسری اشیاء نمونه داره که درطول حیات برنامه اعتبار دارن و با اینکار، پایهی خوبی برای رابط کاربری برنامه فراهم میکنه و به شما یه مکان اولیه برای قراردادن نمونههای خودتون و اختصاص طولعمر مناسب بهشون رو ارائه میکنه.
جمعبندی و نتیجهگیری
درزمانی که داریم ساخت یه برنامهی شئگرا رو توی ذهنمون مجسم میکنیم تا یه کار خاص رو برامون انجام بده، باید «ذات اشیاء» همیشه توی ذهنمون باشه. دو مفهوم بهنام «انواع شئ» و «نمونهها» وجود دارن. یه نوع، مجموعهای از متدهای که مشخص میکنه تمام نمونههای اون نوع میتونن انجام بدن (گردآوری قابلیتها). نمونههای مختلف از یک نوع مشابه، فقط توی مقادیر و خصوصیاتشون با هم فرق دارن (نگهداری وضعیت). بنابراین ما باید با این دیدگاه طراحی برنامهمون رو انجام بدیم. اشیاء یه ابزار برای سازماندهی کدها هستن: مجموعهای از جعبهها برای نگهداری کدی که یه کار مشخص رو انجام میده. همچنین یه ابزار مفهومی هم محسوب میشن. برنامهنویس مجبوره که به اونها درقالب اشیاء مستقل نگاه کنه و اهداف و رفتار برنامه رو به وظایف کوچکتر و مجزا از هم تفکیک کنه و هر وظیفه رو بعهدهی یه شئ مناسب بگذاره.
در همین حال، باید حواسمون باشه که اشیاء ما، جزیرههای جدا از هم نیستن. اشیاء میتونن با همدیگه همکاری کنن و ارتباط داشته باشن؛ یعنی برای همدیگه پیام بفرستن. روشهایی که خطوط مختلف ارتباطی رو میتونیم ترسیم کنیم، قابل شمارش نیست. رسیدن به یه ترکیب مناسب (معماری برنامه) برای ارتباط مناسب و تنگاتنگ و کارآمد بین اشیاء، یکیاز بزرگترین چالشهای برنامهنویسی شئگرا محسوب میشه که بهمرور با افزایش دانش شما در این زمینه و کسب تجربه در برنامهنویسی، به یک مهارت باارزش برای شما تبدیل میشه. توی برنامهنویسی iOS، فریمورک Cocoa (کاکائو) بعنوان یه شتابدهنده برای شما عمل میکنه که مجموعهی ابتدایی از انواع شئ و یک معماری ساده و اولیه و درعینحال کاربردی رو در اختیارتون میگذاره.
استفادهی کارآمد از برنامهنویسی شئگرا برای وادارکردن یه برنامه به انجام کاری که مدنظر شماست، همزمان با نگهداری کد بصورت تمیز و قابل نگهداری و توسعه، خودش بهتنهایی یکی از مهارتهای انکارنشدنی یه برنامهنویس محسوب میشه. مهارتهای شما بهمرور با کسب تجربه افزایش پیدا میکنن. در انتهای این بخش از آموزشمون، پیشنهاد میکنم دو کتاب قدیمی ولی باارزش رو در این زمینه مطالعه کنید. کتاب Refactoring (اصلاح و بهینهسازی کد بدون ایجاد مشکل در اجرای برنامه) نوشتهی Martin Fowler (انتشارات Addison-Wesley در سال 1999) به شما توضیح میده که چرا نیاز به بازنویسی متدهای خاصی از برخی از کلاسهای خودتون دارین و چطور اینکار رو بدون ترس و نگرانی بابت بروز مشکل در کارکرد برنامه انجام بدین. کتاب Design Patterns (الگوهای طراحی) نوشتهی Erich Gamma و Richard Helm و Ralph Johnson و John Vlissides (که بهاسم Gang of Four یا GoF هم شناخته میشن)، مرجع کامل تعریف معماری برنامههای شئگرا محسوب میشه که تمام راههایی که میتونین اشیائتون رو سازماندهی کنین، همراهبا قدرتها و اطلاعات کافی از مزایا و معایب هر روش نسبت به روشهای دیگه (و حتی ترکیب این روشها) رو به شما آموزش میده. این کتاب هم توسط انتشارات Addison-Wesley در سال 1994 منتشر شده.