یکی دیگر از ویژگی های جالب شیء گرایی در تایپ اسکریپت، مبحث متدها و خصوصیات استاتیک هستند. متدها و خصوصیات استاتیک در جاوا اسکریپت ES6 و بالاتر تعریف شده اند اما در نسخه های قدیمی تر مانند ES5 پشتیبانی نمی شوند. متدها و خصوصیات استاتیک روی instance ها یا نمونه های ساخته شده از کلاس ها پیاده نمی شوند بنابراین برای اجرا یا دسترسی به آن ها نیازی به نمونه سازی با کلیدواژه new نداریم. این ویژگی معمولا برای توابع کمکی یا ثابت های سراسری و ... استفاده می شوند.
بگذارید از خود جاوا اسکریپت یک مثال برایتان بزنم. در جاوا اسکریپت شیء ای به نام Math وجود دارد که خصوصیتی به نام PI دارد، همان عدد پی (3.14) خودمان:
Math.PI
همانطور که می دانید ما می توانیم بدون استفاده از کلیدواژه new و نمونه سازی، از این خصوصیت ثابت (عدد پی) استفاده کنیم. همچنین یک متد درون شیء Math وجود دارد که برای اعداد توان میگذارد:
Math.pow();
ما از متد pow بدون نمونه سازی از کلاس Math استفاده کرده ایم. حالا اگر به کد خود برگردیم شاهد قسمت هایی هستیم که می توانند بهینه سازی شوند. به طور مثال ما درون Department (کلاس پدر) نیاز به متدی داریم که کارمندان را برای ما بسازد. چنین متدی بهتر است استاتیک باشد چرا که ما معمولا هیچ وقت از Department نمونه سازی نمی کنیم. نمونه سازی معمولا از کلاس های جزئی تر و خاص تر مثل ITDepartment و Accounting اتفاق می افتد. ساختن یک نمونه جدید از Department فقط برای صدا زدن یک متد اصلا عقلانی نیست. به همین دلیل می توانیم درون Department بگوییم:
abstract class Department { protected employees: string[] = []; constructor(private readonly id: string, public name: string) { } static createEmployee(name: string) { return { name: name }; } // بقیه کد ها //
متد ساده createEmployee با اضافه کردن کلیدواژه static تبدیل به یک متد استاتیک می شود بنابراین استاتیک کردن متدها اصلا کار سختی نیست. من در این متد یک شیء ساده برگردانده ام که خصوصیت name را برابر نام دریافت شده از کاربر قرار می دهد. برای استفاده از این متد می توانیم در انتهای فایل خود و خارج از کلاس به شکل زیر عمل کنیم:
const employee1 = Department.createEmployee('Max');
همانطور که می بینید برای صدا زدن createEmployee نیازی به استفاده از new نداریم بلکه تنها نام کلاس را همراه آن می آوریم. خصوصیات نیز می توانند استاتیک باشند، به طور مثال:
abstract class Department { static fiscalYear = 2020; protected employees: string[] = []; constructor(private readonly id: string, public name: string) { } static createEmployee(name: string) { return { name: name }; } // بقیه کد ها //
همانطور که می بینید خصوصیت fiscalYear با کلیدواژه Static آمده است بنابراین یک خصوصیت استاتیک می باشد. حالا می توانیم برای تست کردن کد ها بگوییم:
const employee1 = Department.createEmployee('Max'); console.log(employee1, Department.fiscalYear);
خروجی کد بالا در کنسول مرورگر به شکل زیر خواهد بود:
{name: “Max”} 2020
نکته: اگر خصوصیت یا متدی را به صورت استاتیک تعریف کنید، فقط در قسمت هایی از خود کلاس می توانید از آن ها استفاده کنید که استاتیک باشند. به طور مثال constructor یک متد استاتیک نیست و هیچگاه هم نمی تواند استاتیک باشد بنابراین اگر بخواهید از fiscalYear (یک خصوصیت استاتیک) درون constructor استفاده کنید، با خطا روبرو می شوید.
برای آشنایی با کلاس های انتزاعی بهتر است به صورت عملی کار کنیم. ابتدا در کلاس Department خصوصیت id را از private به protected تغییر دهید:
abstract class Department { static fiscalYear = 2020; protected employees: string[] = []; constructor(protected readonly id: string, public name: string) { }
با تغییر id (پارامتر constructor) از private به protected کلاس های فرزند نیز به آن دسترسی خواهند داشت. از طرفی یاد گرفتیم که می توانیم متدهای کلاس پدر را overwrite کنیم بنابراین بیایید یک متد به نام describe در کلاس Accounting بنویسیم تا متد Describe در کلاس پدر overwrite شود:
describe() { console.log('Accounting Department - ID: ' + this.id); }
این مسائل را در جلسات قبل یاد گرفته ایم و چیز جدید نیست اما برخی اوقات به جای اینکه بخواهیم توسعه دهندگان اجازه overwrite کردن متدهای کلاس پدر را داشته باشند، می خواهم آن ها را مجبور به این کار کنیم؟ چرا؟ برخی اوقات لازم است که یک متد خاص (مثلا برگرداندن تعداد کارمندان) در تمام کلاس های فرزند از یک کلاس پدر وجود داشته باشد. مثلا قانون شرکت ما این است که هر دپارتمانی باید تعداد کارکنان خودش را بداند بنابراین باید متدی برای محاسبه تعداد ار کارمندان داشته باشد.
شاید بپرسید چرا از همان متد کلاس پدر استفاده نکنیم؟ استفاده از متد موجود در کلاس پدر مشکلی ندارد اما برخی اوقات اجرا و کدنویسی یک متد وابسته به جزئیات موجود در هر کلاس فرزند است و نمی توان یک متد کلی برای تمام کلاس های فرزند نوشت که همه با هم از آن استفاده کنند. مثلا برای محاسبه تعداد کارمندان، هر دپارتمانی تعاریف خاص خودش را دارد و بر اساس آن ها کارمندان خود را محاسبه می کند. بنابراین زمانی که نمی توانیم از یک متد عمومی برای تمام کلاس های فرزند استفاده کنیم مجبوریم از متدهای abstract یا انتزاعی استفاده کنیم.
متدهای انتزاعی معمولا خالی هستند و دیگر کلاس ها را مجبور به تعریف کردن خودشان می کنند. ما می توانیم یک متد ساده را با اضافه کردن کلیدواژه abstract تبدیل به یک متد انتزاعی کنیم اما نکته مهم این است که متدهای انتزاعی فقط در کلاس های انتزاعی قابل پیاده سازی هستند. مثلا من می خواهم متد describe را به یک متد انتزاعی تبدیل کنم. ابتدا باید کلاس پدر را تبدیل به یک کلاس انتزاعی کنم:
abstract class Department { // بقیه کد ها //
در حال حاضر متد describe ما درون کلاس Department به شکل زیر است:
describe(this: Department) { console.log(`Department (${this.id}): ${this.name}`); }
ما باید curly braces ها را از آن حذف کرده و سپس return type کل تابع را مشخص کنیم:
abstract describe(this: Department): void;
متدهای انتزاعی باید خالی باشند و نمی توانید درون آن ها کدی بنویسید چرا که کدنویسی درون آن ها وظیفه کلاس های فرزند است. Void هم تایپ برگشتی این متد است که من تعیین کردم. با انجام این کار در کلاس هایی مثل ITDepartment خطا دریافت می کنیم. تایپ اسکریپت به ما می گوید متد انتزاعی در کلاس پدر وجود دارد که توسط این کلاس پیاده سازی نشده است.
برای حل این خطا باید این متد را درون ITDepartment تعریف کنیم:
class ITDepartment extends Department { admins: string[]; constructor(id: string, admins: string[]) { super(id, 'IT'); this.admins = admins; } describe() { console.log('IT Department - ID: ' + this.id); } }
من اینجا id این دپارتمان را console.log کرده ام اما شما می توانید هر کاری که دوست داشتید را انجام بدهید. توجه داشته باشید که شما می توانید خصوصیات انتزاعی نیز داشته باشید. مثلا:
protected abstract employees: string[];
یادتان باشد که دقیقا مانند متدها، نباید به این خصوصیات مقدار واقعی بدهید. این کار بر عهده کلاس های فرزند است. همچنین باید توجه داشت که کلاس هایی که abstract باشند اجازه instantiate شدن (نمونه سازی با کلیدواژه new) را ندارند. تنها وظیفه کلاس های abstract این است که دیگر کلاس ها از آن ها ارث بری کنند، همین!
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.