برنامه‌نویسی شی‌گرا در TypeScript با کلاس‌ها

کلاس‌ها در تایپ‌اسکریپت

کلاس‌ها یک انتزاع رایج در برنامه‌نویسی شیءگرا (OOP) هستند که برای توصیف ساختار داده‌ها به نام آبجکت‌ها استفاده می‌شوند. این آبجکت‌ها ممکن است شامل وضعیت اولیه و رفتارهایی باشند که به آن نمونه خاص مرتبط هستند. در سال ۲۰۱۵، ECMAScript 6 یک سینتکس جدید برای ایجاد کلاس‌ها در جاوااسکریپت معرفی کرد که با استفاده از ویژگی‌های پروتوتایپ زبان پیاده‌سازی شده است. تایپ‌اسکریپت از این سینتکس به طور کامل پشتیبانی می‌کند و علاوه بر آن ویژگی‌هایی مثل تعیین سطح دسترسی اعضا، کلاس‌های انتزاعی، کلاس‌های جنریک، متدهای تابع پیکان و چند مورد دیگر را اضافه کرده است.

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

ایجاد کلاس

شما می‌توانید با استفاده از کلمه کلیدی class به همراه نام کلاس، یک کلاس تعریف کنید. به عنوان مثال:

class Person {
  // class body
}

برای ایجاد یک نمونه از کلاس، از new به همراه نام کلاس استفاده کنید:

const personInstance = new Person();

می‌توانید کلاس را به عنوان قالبی برای ایجاد آبجکت‌ها در نظر بگیرید که هر نمونه، یک آبجکت جداگانه با شکل مشخص شده دارد.

سازنده (Constructor)

معمولاً هنگام کار با کلاس‌ها نیاز دارید تا یک متد سازنده تعریف کنید. این متد هر بار که یک نمونه جدید از کلاس ایجاد می‌شود اجرا می‌گردد و می‌تواند برای مقداردهی اولیه استفاده شود.

مثال افزودن سازنده به کلاس Person:

class Person {
  constructor() {
    console.log("Constructor called");
  }
}

برای پارامتر دادن به سازنده، آن‌ها را در پرانتز تعریف کنید:

class Person {
  constructor(name: string) {
    console.log(name);
  }
}

const personInstance = new Person("Jane");

پارامتر name اجباری است و اگر موقع ایجاد نمونه آن را وارد نکنید، کامپایلر تایپ‌اسکریپت خطا می‌دهد.

ویژگی‌ها (Properties)

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

class Person {
  name: string;

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

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

می‌توانید مقدار پیش‌فرض هم برای ویژگی تعیین کنید، مثل نمونه زیر که زمان ایجاد نمونه را ذخیره می‌کند:

class Person {
  name: string;
  instantiatedAt: Date = new Date();

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

خصوصی‌سازی و حیطه دید اعضا (Visibility)

تایپ‌اسکریپت به شما اجازه می‌دهد سطح دسترسی اعضای کلاس را مشخص کنید که به سه حالت public، protected و private تقسیم می‌شود:

  • public: اعضا از هر جایی قابل دسترسی هستند؛ این حالت پیش‌فرض است.
  • protected: اعضا فقط درون خود کلاس یا کلاس‌های فرزند قابل دسترسی هستند.
  • private: اعضا فقط درون کلاس تعریف شده قابل دسترسی بوده و حتی زیرکلاس‌ها به آن‌ها دسترسی ندارند.

نمونه‌ای از سطح دسترسی protected:

class Employee {
  protected identifier: string;

  constructor(id: string) {
    this.identifier = id;
  }
}

class FinanceEmployee extends Employee {
  getId() {
    return this.identifier;
  }
}

اگر بخواهید از خارج کلاس یا زیرکلاس به این ویژگی identifier دسترسی داشته باشید، تایپ‌اسکریپت خطا می‌دهد.

کوتاه‌نویسی ویژگی‌ها با پارامترهای سازنده (Parameter Properties)

تایپ‌اسکریپت به شما امکان می‌دهد ویژگی‌هایی که نام‌شان با پارامتر‌های سازنده یکی است را در همان بخش پارامترها تعریف کنید تا کدتان خلاصه‌تر شود:

class Person {
  constructor(public name: string, public age: number) {
    // No need to explicitly assign these values
  }
}

واسط‌ها (Interfaces) و کلاس‌های انتزاعی (Abstract Classes)

تایپ‌اسکریپت این امکان را فراهم کرده تا شکل یک کلاس یا شیء را از طریق واسط‌ها تعریف کنید و همچنین با کلاس‌های انتزاعی امکان ایجاد کلاس‌هایی وجود دارد که خود نمونه‌سازی نمی‌شوند و صرفاً پایه‌ای برای کلاس‌های دیگر هستند.

تعریف واسط Logger برای لاگ گرفتن:

interface Logger {
  debug(message: string, metadata?: Record<string, unknown>): void;
  info(message: string, metadata?: Record<string, unknown>): void;
  warning(message: string, metadata?: Record<string, unknown>): void;
  error(message: string, metadata?: Record<string, unknown>): void;
}

یک کلاس می‌تواند این واسط را پیاده‌سازی کند:

class ConsoleLogger implements Logger {
  debug(message: string, metadata?: Record<string, unknown>) {
    console.debug(message, metadata);
  }
  info(message: string, metadata?: Record<string, unknown>) {
    console.info(message, metadata);
  }
  warning(message: string, metadata?: Record<string, unknown>) {
    console.warn(message, metadata);
  }
  error(message: string, metadata?: Record<string, unknown>) {
    console.error(message, metadata);
  }
}

کلاس‌های انتزاعی می‌توانند برخی اعضا را بدون پیاده‌سازی (abstract) تعریف کنند و زیرکلاس‌ها ملزم به پیاده‌سازی آن‌ها هستند:

abstract class Stream {
  abstract read(): Buffer;
  abstract write(data: Buffer): void;

  copy(targetBuffer: Buffer, targetBufferOffset: number) {
    const bytes = this.read();
    // copy logic
  }
}

class FileStream extends Stream {
  read() {
    // implementation
    return new Buffer();
  }
  write(data: Buffer) {
    // implementation
  }
}

در صورتی که کلاس فرزند هرکدام از متدهای انتزاعی را پیاده‌سازی نکند، تایپ‌اسکریپت خطا می‌دهد.

استفاده از توابع پیکان در متدها

برای حل مشکل تغییر مقدار this در متدهای عادی، می‌توانید از تابع پیکان برای تعریف متدهای کلاس استفاده کنید تا this همیشه به نمونه کلاس اشاره کند:

class Employee {
  identifier: string;

  constructor(id: string) {
    this.identifier = id;
  }

  getIdentifier = () => {
    return this.identifier;
  };
}

const employee = new Employee("123");
const obj = { getId: employee.getIdentifier };

console.log(obj.getId()); // Works correctly

استفاده از کلاس‌ها به عنوان نوع

کلاس‌ها در تایپ‌اسکریپت هم نوع هستند و هم مقدار. بنابراین می‌توانید از نام کلاس به عنوان نوع در امضای توابع استفاده کنید:

function printEmployeeIdentifier(employee: Employee) {
  console.log(employee.identifier);
}

همچنین تایپ‌اسکریپت در مقایسه انواع بر اساس ساختار (structural) آن‌ها عمل می‌کند، بنابراین کلاس‌هایی با شکل مشابه می‌توانند جایگزین یکدیگر شوند.

استفاده از نوع خاص this در داخل کلاس

اگر بخواهید متدی داشته باشید که فقط اشیاء از نوع دقیق کلاس فعلی (نه زیرکلاس‌های دیگر) را بپذیرد، می‌توانید از نوع خاص this در پارامترها استفاده کنید:

class Employee {
  identifier: string;

  constructor(id: string) {
    this.identifier = id;
  }

  isSameEmployeeAs(other: this) {
    return this.identifier === other.identifier;
  }
}

استفاده از نوع کلاس به جای نمونه

گاهی لازم است کلاس را به جای نمونه آن به تابع پاس دهید، برای مثال ساخت تابع کارخانه (factory) برای تولید نمونه‌های جدید:

function createEmployee(ctor: typeof Employee, id: string) {
  return new ctor(id);
}

const newEmployee = createEmployee(Employee, "124");

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

function createEmployee(ctor: new (id: string) => Employee, id: string) {
  return new ctor(id);
}

جمع‌بندی

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

ممنون که با پارمین کلود همراهید.

Click to rate this post!
[Total: 0 Average: 0]

نظرات کاربران

دیدگاهی بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *