دو مفهوم interface و کلاس جزء مفاهیم اساسی برنامه نویسی شی‌گرا (OOP) هستند. تایپ اسکریپت یک زبان جاوااسکریپتی شی‌گرا است که از ES6 به بعد از ویژگی‌های OOP مانند interface، کلاس و کپسوله‌سازی پشتیبانی می‌کند.

سوالی که ممکن است مطرح شود این است که چه زمانی باید از interfaceها، کلاس‌ها و یا هر دو به طور همزمان استفاده کنیم؟ در این مقاله قصد داریم تا با انواع interface و کلاس آشنا شویم و یاد بگیریم که چه زمانی باید از یک یا هر دوی آن‌ها در تایپ اسکریپت استفاده کنیم.

منظور از کلاس در تایپ اسکریپت چیست؟

قبل از شروع، باید بدانیم کلاس تایپ اسکریپت چیست. در برنامه نویسی شی‌گرا، کلاس یک طرح یا قالب است که به وسیله آن می‌توانیم آبجکت‌هایی با ویژگی‌ها و متدهای خاص ایجاد کنیم.

تایپ اسکریپت سینتکس اضافی برای بررسی تایپ را ارائه می‌کند و کد را به جاوااسکریپتی که روی هر پلتفرم و مرورگری اجرا می‌شود تبدیل می‌نماید. کلاس‌ها در تمامی مراحل دخالت دارند. پس از تبدیل کد تایپ اسکریپت به یک فایل جاوااسکریپت، می‌توانیم آن‌ها را در فایل‌های نهایی پیدا کنیم.

کلاس الگوی آبجکت را تعریف می‌کند، یا اینکه آبجکت چیست و چه کاری انجام می‌دهد را تعیین می‌کند. در ادامه یک کلاس ساده با یک سری ویژگی‌ها و متدها ایجاد می‌کنیم تا بتوانیم رفتار آن را ببینیم.

ابتدا از طریق کد زیر یک کلاس Developerایجاد می‌کنیم:

class Developer {
  name?: string; // string or undefined
  position?: string; // string or undefined
}

ما کلاس را با ویژگی‌های nameو positionتوصیف می‌کنیم. آن‌ها تایپ‌هایی مانند stringو undefinedدارند.

در مرحله بعد، از طریق کلاس Developerو با استفاده از کلمه کلیدی newیک آبجکت ایجاد می‌کنیم:

const developer = new Developer();
developer.name // it outputs undefined
developer.position // it outputs undefined

هنگامی که developer.nameرا فراخوانی می‌کنیم، مقدار undefinedرا برمی‌گرداند زیرا مقادیر اولیه را تعیین نکرده‌ایم. برای ایجاد یک آبجکت با مقادیر اولیه در تایپ اسکریپت می‌توانیم از متد constructor استفاده کنیم. متد constructor برای مقداردهی اولیه و ایجاد آبجکت‌ها استفاده می‌شود.

اکنون کلاس Developerخود را با استفاده از کد زیر به روز رسانی می‌کنیم:

class Developer {
  name: string; // only string
  position: string; // only string

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

در کد بالا، متد constructorرا برای مقداردهی اولیه ویژگی‌ها اضافه کردیم.

اکنون می‌توانیم با استفاده از کد زیر nameرا به عنوان Gapurو positionرا به عنوان Frontend Developerتنظیم کنیم:

const developer = new Developer("Gapur", "Frontend Developer");
developer.name // it outputs Gapur
developer.position // it outputs Frontend Developer

در نهایت، همانطور که قبلاً هم اشاره کردیم، کلاس متدهایی دارد که تعیین می‌کند آبجت چگونه باید عمل کند. در این حالت، هر developerای برنامه‌هایی را develop می‌کند. بنابراین، کلاس Developerدارای متد developاست.

یک آبجکت developerمی‌تواند یک اکشن develop را انجام دهد:

class Developer {
  name: string;
  position: string;

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

  develop(): void {
    console.log('develop an app');
  }
}

اگر متد developرا اجرا کنیم، عبارت console.logزیر را اجرا می‌کند:

developer.develop() // it outputs develop an app

منظور از interface در تایپ اسکریپت چیست؟

interface در تایپ اسکریپت ساختاری است که مانند یک قرارداد در برنامه و یا سینتکسی برای کلاس عمل می‌کند. interface همچنین به عنوان duck printing و یا subtyping نیز شناخته می‌شود.

interface شامل یک متد onlyو تعریف‌های فیلد بدون پیاده‌سازی می‌باشد. ما نمی‌توانیم از آن برای ایجاد چیزی استفاده کنیم. کلاسی که یک interface را پیاده‎‌سازی می‌کند باید تمام فیلدها و متدها را داشته باشد. بنابراین، ما آن را برای بررسی تایپ به کار می‌گیریم.

هنگامی که تایپ اسکریپت تمام کدها را به جاوااسکریپت تبدیل می‌کند، interface از فایل جاوااسکریپت محو می‌شود. بنابراین، ابزار مفیدی در فار توسعه به شمار می‌آید.

ما باید از یک interface برای موارد زیر استفاده کنیم:

اکنون می‌خواهیم با کمک کد زیر یک interface تعریف کنیم:

interface InterfaceName {
  // variables;
  // methods;
}

ما فقط می‌توانیم تعریف‌هایی از متغیرها و متدها را در بدنه interface داشته باشیم. در ادامه می‌خواهیم یک interface IDeveloperبرای کلاس Developerقبلی ایجاد کنیم:

interface IDeveloper {
  name: string
  position: string
  develop: () => void
}

class Developer implements IDeveloper {
  name: string;
  position: string;

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

  develop(): void {
    console.log('develop an app');
  }
}

در کد بالا، interface IDeveloperما شامل nameو positionمتغیرها است. همچنین متد developرا هم شامل می‌شود. در نتیجه کلاس Developerinterface IDveloperرا پیاده سازی می‌کند. بنابراین، باید دو متغیر و یک متد تعریف کند.

اگر کلاس Developerهیچ متغیری را پیاده سازی نکند، تایپ اسکریپت یک خطا نشان می‌دهد:

class Developer implements IDeveloper {
  // error Class 'Developer' incorrectly implements interface 'IDeveloper'.
  name: string;

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

  develop(): void {
    console.log('develop an app');
  }
}

تفاوت بین Interfaceها و کلاس‌ها

اکنون سوالی که مطرح می‌شود این است که در تایپ اسکریپت چه زمانی باید از کلاس و چه زمانی از interface استفاده کنیم؟

قبل از پاسخ به این سوال می‌خواهیم در مورد ویژگی staticتایپ اسکریپت صحبت کنیم که به ما این امکان را می‌دهد تا از فیلدها و متدهای کلاس‌ها بدون ایجاد نمونه کلاس استفاده کنیم.

اکنون قصد داریم تا با استفاده از کلاس Developerقبلی یک کلاس با متد static بسازیم:

class Developer {
  static develop(app: { name: string, type: string }) {
    return { name: app.name, type: app.type };
  }
}

اکنون می‌توانیم متد Developer.develop()را بدون نمونه‌سازی کلاس فراخوانی کنیم:

Developer.develop({ name: 'whatsapp', type: 'mobile' })
// outputs: { "name": "whatsapp", "type": "mobile" }

همچنین می‌توانیم از کلاس‌ها برای بررسی تایپ در تایپ اسکریپت استفاده کنیم. در ادامه با استفاده از کد زیر یک کلاسAppایجاد می‌کنیم:

class App {
  name: string;
  type: string;

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

کلاسDeveloperخود را تغییر می‌دهیم:

class Developer {
  static develop(app: App) {
    return { name: app.name, type: app.type }; // output the same
  }
}

اکنون یک نمونه از Appرا می‌سازیم و Developer.develop()را با یک argument object فراخوانی می‌کنیم:

const app = new App('whatsapp', 'mobile');
Developer.develop(app);
// outputs the same: { "name": "whatsapp", "type": "mobile" }

Developer.develop(app)و Developer.develop({ name: 'whatsapp', type: 'mobile' })هر دو محتوای یکسانی را تولید می‌کنند. اما رویکرد دوم خواناتر است و انعطاف‌پذیری بیشتری دارد.

علاوه بر این می‌توانیم نوع آرگومان‌ها را هم بررسی کنیم. برای انجام این کار باید یک آبجکت ایجاد کنیم. اینجاست که مفهوم interfaceها مطرح می‌شود.

ابتدا می‌خواهیم کلاس Appرا با استفاد از کد زیر به یک interface تغییر دهیم:

interface App {
  name: string
  type: string
}

class Developer {
  static develop(app: App) {
    return { name: app.name, type: app.type }; // output the same
  }
}

در کد بالا، بدنه کلاس Developerرا تغییر ندادیم و نمونه‌ای از Appایجاد نکردیم، اما نتیجه یکسان بود. همینطور زمان کوتاه‌تر بوده و تعداد خط کد نیز کم‌تر از قبل می‌باشد.

جمع‌بندی

چه زمانی باید از کلاس‌ها و interfaceها استفاده کنیم؟ به طور خلاصه اگر می‌خواهیم یک type-checked class object ایجاد و ارسال کنیم، باید از کلاس‌های تایپ اسکریپت استفاده کنیم. اما اگر نیاز داریم بدون ایجاد آبجت کار کنیم، استفاده از interfaceها بهترین انتخاب است.

در نهایت، ما دو رویکرد مفید blueprint و contract را بررسی کردیم که می‌توانیم بسته به شرایط پروژه از هر دوی آن‌ها باهم و یا به صورت جداگانه استفاده کنیم.