نکاتی تکمیلی از interface ها

TypeScript - Interface

11 مرداد 1399
نکاتی تکمیلی از interface ها

دسترسی به access modifier ها در interface ها

حالا که می دانیم interface چیست باید به نکات تکمیلی آن بپردازیم. اولین نکته عدم دسترسی interface ها به Access modifier است. به عبارت دیگر شما اجازه ندارید درون یک interface خصوصیتی تعریف کنید که private یا protected یا public باشد. البته در حالت پیش فرض public است و منظور من این است که نمی توانید از کلیدواژه public استفاده کنید. تنها استثناء در این زمینه readonly است:

interface Greetable {
  readonly name: string;
  greet(phrase: string): void;
}

اضافه کردن readonly به خصوصیت name یعنی هر کلاسی یا شیء ای که از ساختار این interface پیروی می کند، باید این خصوصیت را فقط یک بار تعیین و مقداردهی کند و از آن به بعد name هیچ وقت قابل تغییر نیست. همین ساختار در خصوص type های شخصی نیز صادق است:

type Greetable = {
  readonly name: string;
  greet(phrase: string): void;
}

وراثت در interface ها

نکته بعدی مسئله وراثت در interface ها است. مسئله وراثت در interface ها نیز قابل پیاده سازی است. به طور مثال فرض کنید interface بالا (greetable) را به دو interface دیگر و به شکل زیر تقسیم کنیم:

interface Named {
  readonly name: string;
}

interface Greetable {
  greet(phrase: string): void;
}

حالا می توانیم از هر دو interface در کلاس خودمان استفاده کنیم:

class Person implements Greetable, Named {
  name: string;
  age = 30;

  constructor(n: string) {
    this.name = n;
  }

  greet(phrase: string) {
    console.log(phrase + ' ' + this.name);
  }
}

چنین کاری با اینکه جواب می دهد اما ارث بری نیست. فرض کنید ما بخواهیم تمام اشیائی که بر اساس greetable ساخته می شوند، خصوصیت نام (name) را داشته باشند. در چنین حالتی می توان گفت interface دوم (greetable) باید interface اول (named) را extend کند:

interface Named {
  readonly name: string;
}

interface Greetable extends Named {
  greet(phrase: string): void;
}

با این کار کلاس ما فقط لازم است که greetable را implement کند:

class Person implements Greetable {
  name: string;
  age = 30;
// بقیه کدها //

اگر در کلاس بالا خصوصیت name را حذف کنیم سریعا خطا می گیریم. چرا؟ با اینکه ما فقط greetable را implement کرده ایم، اما خود greetable خصوصیت name را از named به ارث می برد و باید در کلاس حضور داشته باشد. بنابراین با Extend کردن interface ها آن ها را جمع کرده و در یک interface واحد قرار می دهیم.

سوال: آیا موقعیتی وجود دارد که در آن واقعا interface ها را extend کنیم؟ چرا همه قوانین interface ها را درون یک interface واحد قرار ندهیم؟

پاسخ: برخی اوقات در کدنویسی می خواهید برخی از اشیاء یا کلاس ها دارای یک خصوصیت خاص باشند (مثلا name) اما نیازی به متد greet ندارید بنابراین فقط named را implement می کنید اما در برخی اوقات نیز ممکن است به هر دو نیاز داشته باشید بنابراین یکی را extend کرده و آن را implement می کنید. اگر تمام کدها را درون یک interface قرار بدهیم، چنین آزادی عملی نخواهیم داشت.

نکته جالب اینجاست که در ارث بری interface ها، برخلاف کلاس ها، وراثت به صورت تکی نیست بلکه هر interface می تواند از چندین interface دیگر ارث بری داشته باشد. به طور مثال:

interface Greetable extends Named, AnotherInterface, ThirdInterface {
  greet(phrase: string): void;
}

تعیین ساختار توابع با interface ها

تا اینجای کار می دانیم که از interface ها برای تعیین ساختار یک شیء یا کلاس استفاده می شود اما این دو مورد تنها موارد قابل استفاده نیستند بلکه ما می توانیم از interface ها برای تعیین ساختار توابع نیز استفاده کنیم. این کار معادل استفاده از function type ها است که در مورد آن ها صحبت کرده ایم. اگر یادتان باشد ما می توانستیم ساختار یک تابع را با استفاده از روش زیر تعیین کنیم:

type AddFn = (a: number, b: number) => number;

این تعریف به ما می گوید که تایپ AddFn باید دو پارامتر عددی بگیرد و یک عدد را نیز برگرداند. حالا می توانیم بگوییم:

let add: AddFn;

یعنی تابعی که درون add قرار می گیرد باید از طرح AddFn پیروی کند. نهایتا هم یک تابع را با توجه به این ساختار تعریف می کنیم:

add = (n1: number, n2: number) => {
  return n1 + n2;
};

مسئله اینجاست که توابع در جاوا اسکریپت، شیء هستند بنابراین می توانیم از interface ها نیز برای انجام همین کار استفاده کنیم:

interface AddFn {
  (a: number, b: number): number;
}

این کد دو قسمت دارد: قسمت اول که پرانتزهای ما هستند و پارامتر های ورودی تابع را مشخص می کنند و قسمت دوم که پس از علامت دو نقطه می آید، نوع مقدار برگردانده شده توسط تابع است. بقیه موارد دقیقا مانند قبل است و می توان گفت:

interface AddFn {
  (a: number, b: number): number;
}

let add: AddFn;

add = (n1: number, n2: number) => {
  return n1 + n2;
};

تعیین موارد دلخواه در interface ها

ما در حال حاضر به خوبی با ساختار زیر آشنا هستیم:

interface Named {
  readonly name: string;
}

اما اگر نخواهیم وجود خصوصیت name در اشیاء مورد نظرمان را اجباری کنیم چطور؟ در حال حاضر هر شیء یا کلاسی که named را implement کند باید خصوصیت name را داشته باشد اما شاید برخی اوقات نیازی به name نباشد بلکه یک گزینه دلخواه باشد. برای چنین حالتی ساختار خاصی وجود دارد:

interface Named {
  readonly name?: string;
}

با اضافه کردن علامت سوال قبل از علامت دو نقطه می گوییم که name یک خصوصیت دلخواه است و اگر شیء ای نخواهد آن را پیاده سازی کند مشکلی وجود ندارد. همین کار را می توانیم با متد ها نیز انجام بدهیم:

interface Greetable extends Named {
  greet?(phrase: string): void;
}

البته همانطور که مشاهده می کنید در متد ها علامت سوال قبل از پرانتز اضافه می شود. همین کار عینا در کلاس ها نیز قابل اجرا است. به طور مثال اگر بگوییم خصوصیت name در کلاس ما نیز دلخواه باشد باید از ساختار زیر استفاده کنیم:

class Person implements Greetable {
  name?: string;
  age = 30;
// بقیه کدها //

البته با انجام این کار و دلخواه کردن خصوصیت name باید در constructor و هر قسمتی که از آن استفاده می کند، وجودش را چک کنیم:

  constructor(n?: string) {
    if (n) {
      this.name = n;
    }
  }

اگر چنین کاری انجام ندهیم، ممکن است name وجود نداشته باشد بنابراین کد ما به خطا برمی خورد. اگر دقت کرده باشید در پارامتر ورودی constructor نیز علامت سوال را اضافه کرده ام تا این پارامتر هم دلخواه و آزاد باشد. اگر این کار را انجام بدهید، دیگر نیازی به پاس دادن مقداری به کلاس مورد نظر در هنگام نمونه سازی نخواهیم داشت:

user1 = new Person();

کد بالا به درستی و بدون خطا کار می کند.

پیاده سازی interface ها در جاوا اسکریپت

اگر کدهای بالا را به جاوا اسکریپت کامپایل کنید متوجه نکته جالبی می شوید. هیچ کدام از interface های ما درون فایل جاوا اسکرپیت وجود ندارند. چرا؟ به دلیل اینکه کدهای جاوا اسکریپت توانایی پیاده سازی interface ها را ندارند و برایشان تعریف نشده است. آیا متوجه منظور من شدید؟ می خواهم بگویم که interface ها و اینچنین موارد پیشرفته ای که در تایپ اسکریپت وجود دارد فقط برای ارتقاء کیفیت کدنویسی شما و دوری از خطاها و باگ های مختلف است و اصلا امکان پیاده سازی در جاوا اسکریپت را ندارد. این مسئله به معنی به درد نخور بودن تایپ اسکریپت نیست، بلکه تمامی این ویژگی ها به ما اجازه می دهد به عنوان یک توسعه دهنده به کیفیت بالایی از کدنویسی دست پیدا کنیم و جلوی خطاهای کد خودمان را بگیریم. مسئله مهم اینجاست که تصور نکنید از آنجایی که کدهایتان را با تایپ اسکریپت نوشتید، کدهای جاوا اسکرپیت شما ضد گلوله و بی نقص می شوند.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش جامع تایپ اسکریپت توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

ما را دنبال کنید
اینستاگرام روکسو تلگرام روکسو ایمیل و خبرنامه روکسو