در این جلسه می خواهیم با interface ها آشنا شویم. در ساده ترین تعریف، interface ها ساختار یک شیء را توصیف می کنند و با استفاده از آن ها مشخص می کنیم که یک شیء چطور باید باشد. برای کار با interface ها یک فایل جدید ایجاد کنید تا از صفر شروع کنیم. برای ساخت یک interface از کلیدواژه interface استفاده می کنیم:
interface Person { }
در کد بالا یک interface به نام Person داریم و درون آن کدهایی می نویسیم که مشخص می کند شیء ما چه ساختاری باید داشته باشد. البته برخلاف کلاس ها، interface ها نقشه شیء نیستند که بعدا شیء ای را بر اساس آن ها بسازیم بلکه فقط یک تایپ خاص هستند. به طور مثال:
interface Person { name: string; age: number; }
یعنی هر فرد باید یک نام (رشته ای) و سن (عددی) داشته باشد. توجه داشته باشید که در کد بالا نمی توانید برای name یا age مقدار تعیین کنید چرا که interface ها فقط ساختار اصلی را مشخص می کنند. همچنین می توانیم به interface ها متد نیز اضافه کنیم:
interface Person { name: string; age: number; greet(phrase: string): void; }
متد بالا که یک پارامتر رشته ای را دریافت می کند و نوع برگشتی اش نیز void است. یادتان باشد که greet در کد بالا واقعا یک متد نیست بلکه فقط ساختار کلی یک متد به نام greet را مشخص کرده ایم. حالا می توانیم از این interface برای type check کردن یک متغیر استفاده کنیم. به طور مثال:
let user1: Person;
در اینجا من متغیری به نام user1 دارم که فعلا نمی خواهم به آن مقداری بدهم بلکه می گویم در نهایت ساختار آن باید بر اساس interface تعریفی من باشد (Person). حالا هنگام مقدار دهی باید دقیقا ساختار Person را رعایت کنید:
let user1: Person; user1 = { name: 'Amir', age: 24, greet(phrase: string) { console.log(phrase + ' ' + this.name); } };
همانطور که می بینید شیء ما دارای name و age و متدی به نام greet با پارامتر ورودی greet از نوع رشته است. همچنین return type این متد void است یعنی هیچ چیزی را برنمی گرداند بلکه فقط عبارت ما را Console.log می کند. حالا می توانیم از متد درون این شیء استفاده کنیم:
let user1: Person; user1 = { name: 'Amir', age: 24, greet(phrase: string) { console.log(phrase + ' ' + this.name); } }; user1.greet('Hi there - I am');
با صدا زدن greet به شکل بالا عبارت Hi there – I am Amir در کنسول مرورگر نمایش داده می شود. بنابراین با استفاده از interface می توانیم ساختار دقیق اشیاء را تعیین کنیم.
در این مرحله سوالی پیش می آید: چرا چیزی به اسم interface داریم؟ برای انجام تمام این کار ها می توانستیم از custom type ها (تایپ هایی که خودمان می نوشتیم) استفاده کنیم. مثلا با تغییر جزئی می توانیم کد بالا را از interface به custom type تغییر داد:
type Person = { name: string; age: number; greet(phrase: string): void; }
به جای کلیدواژه interface از type استفاده کرده ایم و علامت = را نیز بعد از Person گذاشته ایم. در جواب این سوال باید بگویم که interface ها و type ها در اکثر مواقع به جای هم به کار برده می شوند و در بسیاری از پروژه ها می توانید از آن ها به جای یکدیگر استفاده کنید، با این حال interface و type یکی نیستند. یکی از بزرگترین تفاوت ها اینجاست که ما می توانیم در type ها از چیزهایی مثل union type ها نیز استفاده کنیم بنابراین type از این نظر منعطف تر است در حالی که interface فقط می توانست ساختار کلی اشیاء را مشخص کند. البته interface ها واضح تر هستند و زمانی که از آن ها استفاده می کنید کاملا مشخص است منظورتان چیست. به همین دلیل برای تعریف تایپ اشیاء بیشتر از interface استفاده می شود تا همه چیز راحت تر و واضح تر باشد.
همچنین ما می توانیم از interface ها به عنوان یک قرار داد برای کلاس ها استفاده کنیم تا بر اساس آن کار کنند. بگذارید با یک مثال توضیح بدهم. فرض کنید یک interface جدید به شکل زیر داشته باشیم:
interface Greetable { name: string; greet(phrase: string): void; }
ما می توانیم این interface را درون کلاس های مورد نظرمان پیاده کنیم تا مطمئن شویم تمام کلاس هایی که این interface را دارند باید یک خصوصیت name و یک متد به نام greet داشته باشند. برای مثال من یک کلاس به نام Person ایجاد می کنم که از ساختار Greetable پیروی کند:
class Person implements Greetable { }
کلیدواژه implement می گوید که Person بر اساس ساختار Greetable کار می کند. تفاوت این مسئله با وراثت این است که هر کلاس فقط می تواند از یک کلاس دیگر ارث بری داشته باشد اما در مورد interface ها، کلاس ها می توانند بر اساس چندین interface کار کنند. در این مورد باید interface ها را با ویرگول از هم جدا کنید. حالا کلاس خود را بر اساس Greetable می نویسم:
class Person implements Greetable { name: string; constructor(n: string) { this.name = n; } greet(phrase: string) { console.log(phrase + ' ' + this.name); } }
این ساختار دقیقا همان ساختاری است که Greetable مشخص کرده است. اگر از آن سرپیچی کنید با خطا مواجه می شوید. البته منظور من از «پیروی از interface» این است که تمام موارد مشخص شده درون interface را داشته باشید، اما اشکالی ندارد که چیز هایی به آن اضافه کنید:
class Person implements Greetable { name: string; age = 30; constructor(n: string) { this.name = n; } greet(phrase: string) { console.log(phrase + ' ' + this.name); } }
در این مثال من خصوصیتی به نام age را اضافه کرده ام که در interface ما وجود ندارد اما هیچ خطایی نمی بینیم چرا که وظیفه شما پیاده سازی Greetable بود و پس از آن می توانید هر چقدر که کد خواستید اضافه کنید. حتی می توانید کلاس بالا را extend کنید.
بنابراین می توان گفت interface ها شبیه به کلاس های انتزاعی (abstract class) هستند با این تفاوت که interface ها هیچ کدی برای پیاده سازی مقادیر ندارند اما در کلاس های انتزاعی قسمت هایی از کد متدهای ساده بودند که خودمان نوشته بودیم و مقدار دهی کرده بودیم. به عبارت ساده تر در interface ها هیچ مقداری تعیین نمی کنید و هیچ کد منطقی نمی نویسید (فقط ساختار را مشخص می کنید) اما در کلاس های انتزاعی می توانیم مقداری کد منطقی و مقدار دهی داشته باشیم.
برای تست کردن این کدها می توانیم به راحتی بگوییم:
let user1: Greetable; user1 = new Person('Amir'); user1.greet('Hi there - I am'); console.log(user1);
اگر کنسول مرورگر خود را باز کنید، متوجه اجرای بدون خطای کدها خواهید شد. ما در خط اول از کد بالا تایپ user1 را روی greetable گذاشته ایم (مقدار دهی نکرده ایم) و سپس در خط بعد یک نمونه از کلاس Person را درون آن قرار داده ایم. البته هیچ خطایی نخواهیم داشت چرا که خود کلاس Person بر اساس Greetable است بنابراین قرارگیری نمونه آن درون user1 هیچ خطایی ایجاد نمی کند.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.