اینترفیس، مانند قراردادی است که یک نوع را تعریف میکند. کامپایلر از اینترفیسها جهت بررسی نوعها و اجبار به رعایت قرارداد استفاده میکند. در این حالت اگر متدها یا خواص معرفی شدهی در نوع اینترفیس، توسط استفاده کننده بکار گرفته نشوند، خطایی توسط کامپایلر گزارش خواهد شد.
از آنجائیکه اینترفیسها به معنای نوعهای سفارشی هستند و جاوا اسکریپت از آنها پشتیبانی نمیکند، توسط کامپایلر TypeScript، به هیچ نوع کد معادلی در جاوا اسکریپت، ترجمه و تبدیل نخواهند شد. کامپایلر TypeScript تنها از آنها جهت بررسی نوعها استفاده میکند.
اینترفیسها به صورت مجموعهای از تعاریف خواص و متدها، بدون پیاده سازی آنها تعریف میشوند. پیاده سازی این اینترفیسها، توسط کلاسها و یا سایر اشیاء صورت خواهند گرفت. برای مثال یک قرارداد اجاره، مشخص میکند که آخر هر ماه چه مقداری را باید پرداخت کرد. اما این قرار داد مشخص نمیکند که چگونه باید این پرداخت صورت گیرد و از هر شخصی به شخص دیگری میتواند متفاوت باشد. به این حالت duck typing هم میگویند. به این معنا که قرار داد، شکل یک شیء را مشخص میکند و تا زمانیکه پیاده سازی کنندهی آن بتواند این قرارداد را تامین کند، میتواند بجای نوع اصلی نیز بکار گرفته شود.
Duck typing چیست؟
duck typing به این معنا است که اگر پرندهای بتواند مانند یک اردک راه برود، شنا کند و صدا در بیاورد، یک اردک نامیده میشود. بنابراین همینقدر که یک شیء بتواند قراردادی را پیاده سازی کند، نوع آن با نوع اینترفیس یکی درنظر گرفته میشود. برای نمونه به مثال ذیل دقت کنید:
در این مثال اینترفیس Duck، متدهایی را تعریف کردهاست که یک Duck میتواند انجام دهد.
در ادامه متغیر و شیءایی بدون تعریف نوع آن ایجاد شدهاست که همان متدهای اینترفیس Duck را پیاده سازی میکند و امضای آنها با امضای متدهای اینترفیس Duck یکی هستند.
سپس متد FlyOverWater تعریف شده که در آن، نوع پارامتر ورودی آن به صورت صریحی به نوع اینترفیس Duck مقید شدهاست.
در سطر بعدی، این متد با دریافت شیء probablyADuck فراخوانی شدهاست و چون این شیء تمام اجزای قرارداد Duck را پیاده سازی کردهاست، مشکلی در اجرای آن نخواهد بود. به این حالت duck typing میگویند.
نحوهی تعریف یک اینترفیس در TypeScript
تعریف یک اینترفیس با واژهی کلیدی interface شروع شده و سپس خواص و متدهای مدنظر این قرارداد، به همراه نوع آنها تعریف خواهند شد:
در این مثال خواص id، title و author اجباری هستند و پیاده سازی کننده موظف است آنها را به همراه داشته باشد.
در اینترفیسهای TypeScript میتوان خواص اختیاری و optional را نیز تعریف کرد. نمونهی آن خاصیت pages در این مثال است که با ? مشخص شدهاست و نمونهی آنرا در حین تعریف پارامترهای اختیاری متدها نیز پیشتر ملاحظه کرده بودید.
تعریف متدها در یک اینترفیس، با مشخص سازی نام آن متد و ذکر یک کولن و سپس مشخص سازی امضای پارامترهای دریافتی انجام میشود. نوع خروجی متد، در سمت راست علامت <= قرار خواهد گرفت.
استفاده از اینترفیسها برای تعریف نوع خروجی توابع
در مثال زیر، متد CreateCustomerID دارای دو پارامتر ورودی از نوعهای رشتهای و عددی است و خروجی آن نیز از نوع رشتهای تعریف شدهاست:
در ادامه تعریف متغیری را مشاهده میکنید که نوع آن، متدی است که با امضای متد CreateCustomerID یکسان است:
به این ترتیب امکان انتساب متد CreateCustomerID به متغیر IdGenerator وجود خواهد داشت:
جهت مدیریت بهتر یک چنین تعریفهایی و همچنین امکان استفادهی مجدد از آنها، میتوان از اینترفیسها کمک گرفت:
اینترفیس StringGenerator نام بهتر و با قابلیت استفادهی مجددی را به نوع متدی که قابل انتساب است به متغیر IdGenerator، تعریف میکند. در اینجا syntax تعریف نوع متد، در اینترفیس StringGenerator اندکی با حالتهای قبلی متفاوت است. در اینجا بجای استفاده از <= جهت مشخص کردن نوع خروجی متد، از کولن استفاده شدهاست.
اکنون میتوان نحوهی تعریف متغیر IdGenerator را به صورت زیر Refactor کرد و تغییر داد:
به عنوان نمونه میتوان یک چنین تغییری را در نحوهی تعریف اینترفیس Book ابتدای بحث و تغییر متد markDamaged آن نیز اعمال کرد.
بسط و توسعهی اینترفیسها
بسط و توسعهی اینترفیسها شبیه به مباحث ارث بری هستند. به این ترتیب که با بسط یک اینترفیس از طریق اینترفیسی دیگر، میتوان به نوعی مرکب رسید:
در این مثال، ابتدا دو اینترفیس منابع و کتابهای یک کتابخانه تعریف شدهاند. سپس اینترفیس جدیدی به نام Encyclopedia با بسط این دو اینترفیس توسط واژهی کلیدی extends ایجاد شدهاست.
این نوع مرکب، علاوه بر دارا بودن خاصیت volume مختص به خودش، اکنون حاوی دو خاصیت موجود در سایر اینترفیسهای ذکر شدهی در قسمت extends نیز هست.
حال اگر متغیر جدیدی را از نوع Encyclopedia تعریف کنیم، جهت برآورده شده تمام اجزای قرارداد، لازم است هر سه خاصیت را مقدار دهی نمائیم:
نوع کلاسها
مبحث کلاسها به صورت جداگانهای در این سری بررسی خواهند شد. اما جهت تکمیل بحث جاری نیاز است اشارهی کوتاهی به آنها شود.
همانطور که عنوان شد، اینترفیسها تنها شکل و قرارداد پیاده سازی یک شیء را تعریف میکنند؛ بدون ارائهی پیاده سازی خاصی از آنها. تا اینجا در بحث جاری، اشیاء را توسط object literals داخل {} تعریف کردیم (مانند متغیر refBook مثال قبل). اما کلاسها روش بهتری برای انجام اینکار و تعریف اشیاء هستند.
در ذیل تعریف اینترفیس کتابدار را با تک متد doWork آن ملاحظه میکنید:
متد doWork دارای پارامتری نیست و خروجی نیز ندارد. سپس با استفاده از واژهی کلیدی class، یک کلاس جدید را ایجاد کردهایم که با استفادهی واژهی کلیدی implements، یک پیاده سازی مشخص از اینترفیس Librarian را ارائه میدهد:
اکنون داخل این کلاس، پیاده سازی خاصی از متد doWork مشخص شدهی در قرارداد و اینترفیس Librarian را مشاهده میکنید.
در ادامه برای ایجاد شیءایی از روی این تعریف، به نحو ذیل عمل میکنیم:
در اینجا متغیر kidsLibrarian از نوع اینترفیس کتابدار تعریف شدهاست. به این معنا که شیءایی که به آن انتساب داده میشود باید این اینترفیس را پیاده سازی کند. این شیء نیز توسط واژهی کلیدی new، نمونه سازی/وهله سازی میشود. در ادامه میتوان به متدها و خواص شیء kidsLibrarian دسترسی یافت و آنها را فراخوانی کرد.
از آنجائیکه اینترفیسها به معنای نوعهای سفارشی هستند و جاوا اسکریپت از آنها پشتیبانی نمیکند، توسط کامپایلر TypeScript، به هیچ نوع کد معادلی در جاوا اسکریپت، ترجمه و تبدیل نخواهند شد. کامپایلر TypeScript تنها از آنها جهت بررسی نوعها استفاده میکند.
اینترفیسها به صورت مجموعهای از تعاریف خواص و متدها، بدون پیاده سازی آنها تعریف میشوند. پیاده سازی این اینترفیسها، توسط کلاسها و یا سایر اشیاء صورت خواهند گرفت. برای مثال یک قرارداد اجاره، مشخص میکند که آخر هر ماه چه مقداری را باید پرداخت کرد. اما این قرار داد مشخص نمیکند که چگونه باید این پرداخت صورت گیرد و از هر شخصی به شخص دیگری میتواند متفاوت باشد. به این حالت duck typing هم میگویند. به این معنا که قرار داد، شکل یک شیء را مشخص میکند و تا زمانیکه پیاده سازی کنندهی آن بتواند این قرارداد را تامین کند، میتواند بجای نوع اصلی نیز بکار گرفته شود.
Duck typing چیست؟
duck typing به این معنا است که اگر پرندهای بتواند مانند یک اردک راه برود، شنا کند و صدا در بیاورد، یک اردک نامیده میشود. بنابراین همینقدر که یک شیء بتواند قراردادی را پیاده سازی کند، نوع آن با نوع اینترفیس یکی درنظر گرفته میشود. برای نمونه به مثال ذیل دقت کنید:
interface Duck { walk: () => void; swim: () => void; quack: () => void; } let probablyADuck = { walk: () => console.log('walking like a duck'), swim: () => console.log('swimming like a duck'), quack: () => console.log('quacking like a duck') } function FlyOverWater(bird: Duck) { } FlyOverWater(probablyADuck); // works!
در ادامه متغیر و شیءایی بدون تعریف نوع آن ایجاد شدهاست که همان متدهای اینترفیس Duck را پیاده سازی میکند و امضای آنها با امضای متدهای اینترفیس Duck یکی هستند.
سپس متد FlyOverWater تعریف شده که در آن، نوع پارامتر ورودی آن به صورت صریحی به نوع اینترفیس Duck مقید شدهاست.
در سطر بعدی، این متد با دریافت شیء probablyADuck فراخوانی شدهاست و چون این شیء تمام اجزای قرارداد Duck را پیاده سازی کردهاست، مشکلی در اجرای آن نخواهد بود. به این حالت duck typing میگویند.
نحوهی تعریف یک اینترفیس در TypeScript
تعریف یک اینترفیس با واژهی کلیدی interface شروع شده و سپس خواص و متدهای مدنظر این قرارداد، به همراه نوع آنها تعریف خواهند شد:
interface Book { id: number; title: string; author: string; pages?: number; markDamaged: (reason: string) => void; }
در اینترفیسهای TypeScript میتوان خواص اختیاری و optional را نیز تعریف کرد. نمونهی آن خاصیت pages در این مثال است که با ? مشخص شدهاست و نمونهی آنرا در حین تعریف پارامترهای اختیاری متدها نیز پیشتر ملاحظه کرده بودید.
تعریف متدها در یک اینترفیس، با مشخص سازی نام آن متد و ذکر یک کولن و سپس مشخص سازی امضای پارامترهای دریافتی انجام میشود. نوع خروجی متد، در سمت راست علامت <= قرار خواهد گرفت.
استفاده از اینترفیسها برای تعریف نوع خروجی توابع
در مثال زیر، متد CreateCustomerID دارای دو پارامتر ورودی از نوعهای رشتهای و عددی است و خروجی آن نیز از نوع رشتهای تعریف شدهاست:
function CreateCustomerID(name: string, id: number): string { return name + id; }
let IdGenerator: (chars: string, nums: number) => string;
IdGenerator = CreateCustomerID;
interface StringGenerator { (chars: string, nums: number): string; }
اکنون میتوان نحوهی تعریف متغیر IdGenerator را به صورت زیر Refactor کرد و تغییر داد:
let IdGenerator: StringGenerator;
بسط و توسعهی اینترفیسها
بسط و توسعهی اینترفیسها شبیه به مباحث ارث بری هستند. به این ترتیب که با بسط یک اینترفیس از طریق اینترفیسی دیگر، میتوان به نوعی مرکب رسید:
interface LibraryResource { catalogNumber: number; } interface LibraryBook { title: string; } interface Encyclopedia extends LibraryResource, LibraryBook { volume: number; }
این نوع مرکب، علاوه بر دارا بودن خاصیت volume مختص به خودش، اکنون حاوی دو خاصیت موجود در سایر اینترفیسهای ذکر شدهی در قسمت extends نیز هست.
حال اگر متغیر جدیدی را از نوع Encyclopedia تعریف کنیم، جهت برآورده شده تمام اجزای قرارداد، لازم است هر سه خاصیت را مقدار دهی نمائیم:
let refBook: Encyclopedia = { catalogNumber: 1234, title: 'The Book of Everything', volume: 1 }
نوع کلاسها
مبحث کلاسها به صورت جداگانهای در این سری بررسی خواهند شد. اما جهت تکمیل بحث جاری نیاز است اشارهی کوتاهی به آنها شود.
همانطور که عنوان شد، اینترفیسها تنها شکل و قرارداد پیاده سازی یک شیء را تعریف میکنند؛ بدون ارائهی پیاده سازی خاصی از آنها. تا اینجا در بحث جاری، اشیاء را توسط object literals داخل {} تعریف کردیم (مانند متغیر refBook مثال قبل). اما کلاسها روش بهتری برای انجام اینکار و تعریف اشیاء هستند.
در ذیل تعریف اینترفیس کتابدار را با تک متد doWork آن ملاحظه میکنید:
interface Librarian { doWork: () => void; }
class ElementarySchoolLibrarian implements Librarian { doWork() { console.log('Reading to and teaching children...'); } }
در ادامه برای ایجاد شیءایی از روی این تعریف، به نحو ذیل عمل میکنیم:
let kidsLibrarian: Librarian = new ElementarySchoolLibrarian(); kidsLibrarian.doWork();