TypeScript: فئات وواجهات TypeScript

آخر تحديث: 2026-08-26

تُعد الفئات والواجهات الركيزتين الأساسيتين للبرمجة الموجهة للكائنات في TypeScript — حيث توفر الفئات عمليات التنفيذ، بينما تحدد الواجهات الشروط. وعندما تنفذ فئة ما واجهةً ما، يضمن TypeScript أن تفي تلك الفئة بجميع المتطلبات المحددة في الواجهة.

1. فئة ما تُنفِّذ واجهة (تُنفِّذ)

(1) قواعد النحو الأساسية

TYPESCRIPT
interface Printable {
  toString(): string;
}

class User implements Printable {
  constructor(public name: string, public age: number) {}

  toString(): string {
    return `${this.name},${this.age} years old`;
  }
}

let user = new User("Charlie", 20);
console.log(user.toString());  // "Charlie,20 years old"

(2) أهمية تنفيذ الواجهات

الواجهة هي «عقد» — فعندما تنفذ فئة ما واجهةً ما، فإنها تتعهد قائلةً: «سأوفر جميع الخصائص والطرق التي تتطلبها الواجهة»:

TYPESCRIPT
interface Animal {
  name: string;
  speak(): string;
  move(distance: number): void;
}

class Dog implements Animal {
  constructor(public name: string) {}

  speak(): string {
    return "Woof!";
  }

  move(distance: number): void {
    console.log(`${this.name} Moved ${distance}m`);
  }
}

// ❌ Members that do not meet the interface requirements will result in an error.
// class Cat implements Animal {
//   constructor(public name: string) {}
//   // Missing speak and move → Compilation Error
// }

(3) تنفيذ متعدد الواجهات

يمكن للفئة أن تُنفِّذ واجهات متعددة — مفصولة بفواصل:

TYPESCRIPT
interface Serializable {
  serialize(): string;
}

interface Comparable {
  compareTo(other: this): number;
}

class Score implements Serializable, Comparable {
  constructor(public value: number) {}

  serialize(): string {
    return JSON.stringify({ value: this.value });
  }

  compareTo(other: Score): number {
    return this.value - other.value;
  }
}

let s1 = new Score(90);
let s2 = new Score(85);

console.log(s1.serialize());       // '{"value":90}'
console.log(s1.compareTo(s2));     // 5(Positive Number Display s1 > s2)

▶ مثال: تعريف واجهة برمجة تطبيقات موحدة باستخدام الواجهات

TYPESCRIPT
interface Repository<T> {
  findById(id: number): T | null;
  findAll(): T[];
  save(entity: T): void;
  delete(id: number): boolean;
}

interface User {
  id: number;
  name: string;
  email: string;
}

class UserRepository implements Repository<User> {
  private users: User[] = [];

  findById(id: number): User | null {
    return this.users.find(u => u.id === id) ?? null;
  }

  findAll(): User[] {
    return [...this.users];
  }

  save(entity: User): void {
    let index = this.users.findIndex(u => u.id === entity.id);
    if (index >= 0) {
      this.users[index] = entity;  // Update
    } else {
      this.users.push(entity);     // New
    }
  }

  delete(id: number): boolean {
    let len = this.users.length;
    this.users = this.users.filter(u => u.id !== id);
    return this.users.length < len;
  }
}

let repo = new UserRepository();
repo.save({ id: 1, name: "Charlie", email: "xiao@example.com" });
repo.save({ id: 2, name: "Diana", email: "hong@example.com" });

console.log(repo.findById(1)?.name);  // "Charlie"
console.log(repo.findAll().length);   // 2
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
Charlie
2


2. الفئات التي ترث من الواجهات

من السمات الفريدة في TypeScript أن الواجهات يمكنها أن ترث تعريفات العناصر من الفئات (حيث ترث إعلانات الأنواع فقط، وليس عمليات التنفيذ):

TYPESCRIPT
class Control {
  private state: string = "active";

  protected getState(): string {
    return this.state;
  }
}

// Interface-Inherited Classes——Inherit only the type signature
interface Selectable extends Control {
  select(): void;
}

// When a class implements an interface, it must provide all members.
class Dropdown implements Selectable {
  private state: string = "active";  // You must implement it yourself. private Members

  protected getState(): string {
    return this.state;
  }

  select(): void {
    console.log("Selected:" + this.getState());
  }
}
💡 الغرض: تُستخدم الفئات التي ترث الواجهة بشكل أساسي في التسلسلات الهرمية المعقدة للفئات لوصف الأنواع التي «تتمتع ببنية فئة معينة مع امتلاكها في الوقت نفسه قدرات إضافية». ولا تُستخدم هذه الفئات عادةً في عمليات التطوير اليومية.



3. الفئات المجردة (abstract)

الفئة المجردة هي «فئة غير مكتملة» — فهي توفر تنفيذًا جزئيًّا، ويجب على الفئات الفرعية إكمال الباقي:

(1) قواعد النحو الأساسية

TYPESCRIPT
abstract class Shape {
  // Abstract Methods——Not implemented,Subclasses must implement
  abstract getArea(): number;
  abstract getPerimeter(): number;

  // Specific Methods——Implemented,Direct Inheritance by Subclasses
  describe(): string {
    return `Area:${this.getArea().toFixed(2)},Perimeter:${this.getPerimeter().toFixed(2)}`;
  }
}

class Circle extends Shape {
  constructor(public radius: number) {
    super();
  }

  getArea(): number {
    return Math.PI * this.radius ** 2;
  }

  getPerimeter(): number {
    return 2 * Math.PI * this.radius;
  }
}

class Rectangle extends Shape {
  constructor(public width: number, public height: number) {
    super();
  }

  getArea(): number {
    return this.width * this.height;
  }

  getPerimeter(): number {
    return 2 * (this.width + this.height);
  }
}

// ❌ Abstract classes cannot be instantiated directly.
// let shape = new Shape();

let circle = new Circle(5);
let rect = new Rectangle(4, 6);

console.log(circle.describe());  // "Area:78.54,Perimeter:31.42"
console.log(rect.describe());    // "Area:24.00,Perimeter:20.00"

(2) الفئات المجردة مقابل الواجهات

الميزة الفئة المجردة الواجهة
طريقة التنفيذ قد يكون لها طريقة تنفيذ محددة يجب ألا يكون لها أي طريقة تنفيذ
المُنشئ يمكن أن يحتوي على لا يمكن أن يحتوي على
الوراثة المتعددة الوراثة الفردية فقط يُسمح بالوراثة المتعددة (extends)
مُعدِّلات الوصول تدعم public/private/protected الخصائص تكون عامة بشكل افتراضي
إنشاء مثيل لا يمكن إنشاء مثيل مباشرة لا يمكن إنشاء مثيل (نوع خالص)
موجود في وقت التشغيل نعم (تم ترجمته إلى فئة JS) لا (إعلان نوع بحت)

(3) متى تُستخدم الفئات المجردة

(4) متى تُستخدم الواجهات

▶ مثال: تطبيق نمط «طريقة القالب» باستخدام فئة مجردة

TYPESCRIPT
abstract class DataParser {
  // Template Method——Define the Algorithm Framework
  parse(input: string): string {
    let raw = this.read(input);
    let validated = this.validate(raw);
    let transformed = this.transform(validated);
    return this.format(transformed);
  }

  // Specific Methods——Shared by all subclasses
  private read(input: string): string {
    return input.trim();
  }

  // Abstract Methods——Each subclass implements it on its own
  protected abstract validate(data: string): string;
  protected abstract transform(data: string): string;

  // Hook Method——Subclasses may choose to override this method
  protected format(data: string): string {
    return data;
  }
}

class UpperParser extends DataParser {
  protected validate(data: string): string {
    if (data.length === 0) throw new Error("Input is empty");
    return data;
  }

  protected transform(data: string): string {
    return data.toUpperCase();
  }
}

class ReverseParser extends DataParser {
  protected validate(data: string): string {
    return data;
  }

  protected transform(data: string): string {
    return data.split("").reverse().join("");
  }

  protected format(data: string): string {
    return `[Reversal] ${data}`;
  }
}

let upper = new UpperParser();
let reverse = new ReverseParser();

console.log(upper.parse("  hello world  "));     // "HELLO WORLD"
console.log(reverse.parse("  hello world  "));    // "[Reversal] dlrow olleh"
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
HELLO WORLD
[Reversal] dlrow olleh


4. استخدام الفئات كأنواع

يُعد تعريف الفئة بحد ذاته نوعًا — يمكنك استخدام اسم الفئة كنوع لمتغير:

(1) مفهوم الفئة

TYPESCRIPT
class Point {
  constructor(public x: number, public y: number) {}

  distanceTo(other: Point): number {
    return Math.sqrt((this.x - other.x) ** 2 + (this.y - other.y) ** 2);
  }
}

// Class as a Type——Accept Point Instances or structurally compatible objects
let p1: Point = new Point(1, 2);
let p2: Point = { x: 3, y: 4 };   // ✅ Structural Compatibility(But does not include methods!)

console.log(p1.distanceTo(p2));    // ✅ p1 has distanceTo method
// p2.distanceTo(p1);              // ❌ p2 Only x and y,There is no way
⚠️ ملاحظة: لا يحتوي قسم «النوع» في الفئة إلا على خصائص المثيلات وتوقيعات الطرق. عند تعيين كائن متوافق مع البنية (struct)، لا يتم تضمين الطرق — بل تتم مطابقة الخصائص فقط. للوصول إلى الطرق، يجب إنشاء مثيل باستخدام new Point().

(2) نوع الفئة مقابل عامل typeof

TYPESCRIPT
class Factory {
  static create(): Factory {
    return new Factory();
  }

  constructor(public name: string) {}
}

// Factory Type——Instance Type
let instance: Factory = new Factory("Products");

// typeof Factory Type——The class itself(Constructor Types)
let FactoryClass: typeof Factory = Factory;
let another = FactoryClass.create();  // ✅ Calling a Static Method

▶ مثال: تنفيذ واجهات متعددة في فئة واحدة

TYPESCRIPT
interface Loggable {
  log(): string;
}

interface Serializable {
  serialize(): string;
}

class Task implements Loggable, Serializable {
  constructor(
    public id: number,
    public title: string,
    public done: boolean = false
  ) {}

  log(): string {
    return `Task #${this.id}: ${this.title} [${this.done ? "done" : "pending"}]`;
  }

  serialize(): string {
    return JSON.stringify({ id: this.id, title: this.title, done: this.done });
  }
}

let task = new Task(1, "Learn TypeScript");
console.log(task.log());         // "Task #1: Learn TypeScript [pending]"
console.log(task.serialize());   // '{"id":1,"title":"Learn TypeScript","done":false}'
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
Task #1: Learn TypeScript [pending]
{"id":1,"title":"Learn TypeScript","done":false}


❓ أسئلة شائعة

س ما الفرق بين implements وextends؟
ج implements تعني «تنفيذ واجهة» — أي أن الفئة تتعهد بتوفير جميع العناصر المطلوبة في الواجهة، لكنها لا ترث أي كود تنفيذ. extends تعني «تورث من فئة» — حيث ترث الفئة الفرعية كل كود التنفيذ من الفئة الفائقة ويمكنها تجاوزه. يمكن للفئة أن implements واجهات متعددة، لكنها لا يمكنها إلا extends فئة فائقة واحدة.
س أيهما يجب أن أستخدم — فئة مجردة أم واجهة؟
ج هناك قاعدة بسيطة: إذا كانت هناك عدة فئات تحتاج إلى مشاركة كود التنفيذ، فاستخدم فئة مجردة؛ أما إذا كنت تحتاج فقط إلى تعريف عقد يحدد «ما يمكنها فعله»، فاستخدم واجهة. يمكنك الجمع بين الاثنين — حيث تحدد الواجهة القدرات، بينما توفر الفئة المجردة تنفيذًا جزئيًا.
س هل يمكن لفئة تُنفِّذ واجهة أن تحتوي على عناصر إضافية؟
ج نعم. لا يُشترط على الفئة التي تنفذ واجهة إلا أن تحتوي على «على الأقل العناصر المحددة في الواجهة»؛ أما العناصر الإضافية فهي صالحة تمامًا. على سبيل المثال، interface Printable { print(): void } وclass Document implements Printable { print() {...}; save() {...} } — تحتوي الفئة Document على طريقة إضافية هي save، ولكن هذا أمر صالح تمامًا.
س هل يمكن لواجهة أن ترث من فئة مجردة؟
ج نعم. interface ISelect extends AbstractControl {} هذا صحيح — سترث الواجهة جميع توقيعات العناصر من الفئة المجردة (بما في ذلك توقيعات العناصر الخاصة). ومع ذلك، يجب على الفئات التي تنفذ هذه الواجهة أن توفر تنفيذيها الخاصة لجميع العناصر، بما في ذلك العناصر الخاصة.

📖 ملخص

📝 تمارين

  1. المشكلة الأساسية (صعوبة ⭐): عرّف واجهة Loggable (التي تحتوي على طريقة log)، واجعل فئتي User وProduct تنفذانها على التوالي. أنشئ مثيلات واستدعِ الطريقة log.
  2. مشكلة متقدمة (درجة الصعوبة ⭐⭐): حدد فئة مجردة Vehicle (تحتوي على خاصية brand، وطريقة مجردة start، وطريقة ملموسة describe)، ثم أنشئ فئتين فرعيتين Car وBike اللتين تنفذان الطريقة start، على التوالي. اكتب دالة تأخذ معلمة من النوع Vehicle وتستدعي الطريقة describe.
  3. التحدي (الصعوبة: ⭐⭐⭐): صمم واجهة SortableCollection<T> (تحتوي على خاصية length، وطريقة compare، وطريقة swap)، واجعل الفئة NumberCollection تنفذها. ثم، قم بتنفيذ خوارزمية الفرز الفقاعي في الفئة المجردة Sorter (باستخدام طرق الواجهة)، بحيث لا تحتاج الفئات الفرعية سوى إلى توفير تطبيقات ملموسة لـ compare و swap.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%