Dart Const (const constructor)

Flutter Challenge(چالش فلاتر)

قسمت قبلی: Const values.

در ادامه میرسیم به بحث شیرین const constructor ها.

?جلسه قبلی در مورد const values صحبت کردیم که مقدمه ای بود برای درک مفهوم const constructor ها.

?قبلن در مورد اجزای تشکیل دهنده یه کلاس توضیح دادیم و گفتیم که هر کلاس از یک سری فیلد تشکیل میشه (instance fields) و همچنین یک سری تابع که روی این فیلد ها عمل میکنن (methods ). فیلدهایی با عنوان static هم داشتیم که به طور مفصل راجبشون صحبت کردیم و گفتیم که توی یه کلاس فقط فیلد های static قابلیت const شدن دارن و instance field ها نمیتونن const باشن چون که موقع run-time و بعد از ساخته شدم نمونه از کلاس مقداردهی میشن و قابل دسترسی هستن.

?از طرف دیگه گفتیم وقتی که از کلید const قبل یه variable یا value استفاده میکنیم اون قسمت در زمان کامپایل تحلیل و مقدارش ثبت میشه. پس برای صحبت کردن در مورد const constructor ها ابتدا باید constructor رو بشناسیم و ببینیم چه قسمت هایی از یک constructor قابلیت این رو داره که در زمان کامپایل توسط کامپایلر عزیز آنالیز بشه.

سازنده یا Constructor چیه؟

یه متدی داخل کلاس هست که موقع ساخت نمونه از کلاس صدا زده میشه.

عملیات ساخت نمونه از یه کلاس هم که همیشه توی run-time انجام میشه , پس کدهایی که داخل constructor زده میشه موقع run-time اجرا میشن.

ولی همون طور که میدونید وظیفه ی اصلی constructor ها این هستش که instance field های یک کلاس رو مقدار دهی اولیه کنن(initialize).

خب حالا ببینیم این initialize کردن داخل دارت چند روش میتونه داشته باشه:

  • روش اول: استفاده از this در ارگومان ها ورودی سازنده.

?این قسمت توسط کامپایلر قابل رصد شدن هست و کامپایلر میتونه ارگومان های ورودی سازنده های کلاس ها رو چک کنه.

  • روش دوم : استفاده از لیست مقدار دهی(initialize list).

?این قسمت هم توسط کامپایلر قابل رصد هست.

  • روش سوم : استفاده از constructor body.

?این قسمت یعنی constructor body توسط کامپایلر قابل رصد نیست و زمانی که از کلاس مورد نظر نمونه ساخته میشه (run-time) اجرا میشه و از اسرار اون پرده برداشته میشه.


???پس روش های اول و دوم توسط کامپایلر قابل رصد شدن و آنالیز هستن و روش سوم نه.

❓چرا؟

چون که توی روش های اول و دوم فقط instance field ها دارن مقدار دهی میشن و کامپایلر میتونه این رو به راحتی آنالیز کنه ولی توی روش سوم علاوه بر مقدار دهی instance field ها , کد های دیگه هم ممکنه داخل constructor body قرار بگیره که این کد ها فقط و فقط توی run-time و موقع ساخت نمونه باید اجرا بشن.

❓پس با توجه به مفهومی که از const میدونیم برای داشتن یک const constructor کدوم یک از روش های بالا قابل استفاده هستن؟

???افرین... فقط روش اول و دوم. چون که اگه بخوایم یه const constructor داشته باشیم باید قابلیت این رو داشته باشه که زمان کامپایل همه بخش های اون آنالیز بشن.

خب این از نکته اول در ساخت const constructor ها.

اما یه نکته دیگه هم برای ساخت const constructor وجود داره ...

???این که همه instance field های کلاس مورد نظر final باشن. در واقع کلاسمون immutable باشه(یعنی غیر قابل تغییر باشه).

از این نظر کلاس ها به دو دسته تقسیم میشن.

  • Immutable

کلاسی که همه instance field های اون final هست و در واقع اون کلاس قابلیت ویرایش نداره و اگه خواستید یکی از فیلد های اون کلاس رو تغییر بدید باید یه نمونه جدید بسازید.

کلاس بالا رو در نظر بگیرید که از این نوع هست . یه نمونه به اسم p ازش ساختیم. حالا اگه بخوایم مقدار x رو توی این نمونه تغییر بدیم :

میبینید که امکانش نیست و مجبوریم یه نمونه جدید با مقدار x جدید بسازیم.

  • mutable

کلاسی که حداقل یه دونه از instance field های اون final نباشه و قابلیت ویرایش رو داشته باشه.


???پس گفتیم که شرط دوم برای داشتن یه const constructor این هست که کلاسمون immutable باشه.

❓ولی چرا؟

چون که با توجه به مفهوم const قراره که توی زمان کامپایل یه نمونه از اون کلاس ساخته بشه و توی حافظه قرار بگیره و بعد از اون توی run-time هر موقع که یه نمونه با مقادیر مورد نظر از اون کلاس خواستیم دقیقن همون نمونه ساخته شده زمان کامپایل رو بهمون بده. خب حالا اگه نمونه های ساخته شده قابل ویرایش باشن ما نمیتونیم جاهای مختلف که به اون نمونه نیاز داریم از یه نمونه واحد ساخته شده توی compile-time استفاده کنیم.(این رو وقتی مثال بزنیم بهتر متوجه میشید , بعدن دوباره برگردید به این جمله و دوباره بخونید.)


پس یه بار دیگه چیزایی که تا اینجا گفتیم رو مرور میکنیم:

دو تا شرط برای داشتن یه const constructor داریم:

  • کلاس مورد نظر immutable باشه (همه فیلد های اون final باشن.)
  • سازنده مورد نظر body نداشته باشه (از روش اول یا دوم برای مقدار دهی اولیه فیلد ها استفاده کنه.)


خب بریم مثال بزنیم:

توی کد بالا کلاسمون immutable هست و constructor هم از روش اول برای مقداردهی فیلد ها استفاده کرده.

دو نمونه با مقادیر کاملن یکسان ساختیم و اون ها رو با هم مقایسه کردیم و نتیجه false شد.

❓چرا؟

چون که هر بار که درخواست میدیم برای ساخت نمونه , یه نمونه جدید و توی یه مکان جدید از حافظه ساخته میشه و چون که اپراتور == فقط بررسی میکنه که ایا دو نمونه مورد نظر به یک مکان از حافظه اختصاص دارن یا نه نتیجه false میشه.

مثال بعدی:

دو تا تغییر توی کد دادم:

  • اول توی کلاس و قبل constructor از کلید const استفاده کردم.
  • دوم موقع ساخت نمونه از const قبل value ها استفاده کردم.

با تغییر اول constructor مورد نظر قابلیت این رو پیدا میکنه که در زمان کامپایل آنالیز بشه.(اون دو شرط اصلی هم که رعایت شده.)

❓خب حالا چه اتفاقی میوفته؟

کامپایلر وقتی که میرسه به const Point(2 , 4) میره و یه نمونه با این مقادیر ایجاد میکنه و توی حافظه ثبت میکنه و از اون بع بعد توی run-time هر موقع به این عبارت یعنی const Point(2 , 4) رسید میبینه که قبلن یه نمونه با همین مقادیر ثبت کرده و دقیقن همون نمونه رو میریزه توی متغیر. پس هر دو متغیر دقیقن به یک مکان از حافظه اشاره میکنن و نتیجه true میشه.

مثال بعدی :

میبینید که اولین نمونه رو با const ساختم و دومی رو بدون const .(وقتی هیچی نمیذاریم خود دارت به صورت پیش فرض new قرار میده).

شما بگید...

❓چرا نتیجه false شد؟

مثال بعدی :‌

میبینید که طبق این مثال یه کلاس میتونه شامل چنتا constructor باشه که بعضی هاشون const هستن و بعضی هاشون نه.


یه مثال ساده :

الان دو تا نمونه ی ثابت ولی با مقادیر مختلف درخواست شدن پس دو تا نمونه مجزا توی دو تا مکان مجزا از حافظه ساخته میشن...


حالا مثال بعدی رو با توجه به نکاتی که توی همه این جلسات گفتیم تحلیل کن.

❓چرا ارور گرفتیم؟ (زیر x و y خط قرمز داریم.)

چون که داریم از const استفاده میکنیم و موقع کامپایل وقتی const Point(x , y); این رو میبینه میخاد یه نمونه با این مقادیر ایجاد کنه. ولی در کمال ناباوری میبینه که x و y دو تا متغیر final هستن و final ها هم که تازه موقع run-time مقدار میگیرن پس مقدارشون موقع کامپایل مشخص نیست که بخواد با استفاده از اونها نمونه بسازه.

پس نتیجه خیلی مهم این هست که برای ساخت نمونه های const تمام مقادیری که به constructor پاس داده میشن باید compile-time constant باشن.(نکته ای که میدونم توی فلاتر خیلی وقتا خیلی هاتون رو اذیت کرده و به دلیلش فکر نکردید... اگرم دلیلش رو میدونستید که ایولللل.)

این سناریو رو اینجوری در نظر بگیرید که مثلن x و y دو تا مقداری هستن که موقع run-time مشخص میشن , مثلن مقادیری هستن که از سرور گرفته میشن یا از TextField خونده میشن.

پس با وجود اینکه کلاسمون قابلیت این رو داره که از const constructor استفاده کنه ولی بعضی از مواقع نمیتونیم از این قابلیت استفاده کنیم و ناچاریم که از کلاسمون نمونه جدید (new) بسازیم.


پس یه جمع بندی داشته باشیم:

  • برای اینکه یه کلاس const constructor داشته باشه باید immutable باشه(همه instance field هاش final باشن.)
  • همچنین مقدار دهی اولیه به instance field ها یا باید با استفاده از کلمه کلیدی this انجام بشه و یا داخل initialize list.
  • سازنده ای که قراره const بشه نباید body داشته باشه.(چون که body شامل کدها و عملیات مختلف هست و در زمان کامپایل قابل آنالیز نیست و کامپایلر دارت قابلیت آنالیز اون رو نداره.)
  • برای ساخت یه نمونه const از یه کلاس که const constructor داره , باید تمام مقادیری که به constructor پاس داده میشن خودشون compile-time constant باشن.
  • یک کلاس میتونه همزمان شامل constructor های const و غیر const باشه. ولی اگه حداقل یه constructor از نوع const داخل کلاس داشتیم , کلاس باید immutable باشه.

??? مفهوم Widget داخل فلاتر یه مفهوم immutable هست پس همه ویجت هاتون رو immutable بسازید و به const constructor مجهزشون کنید و جاهایی که امکان داره (نه همه جا) نمونه های const ازشون بسازید.(این نکته مقدمه ای هست برای بحث های آینده)


قسمت بعدی: استفاده در فلاتر.

برای دریافت آموزش های بیشتر و شرکت در چالش های فلاتری, کانال فلاتری Flutter Challenge(چالش فلاتر) رو دنبال کن.