TypeScript: فئات وواجهات TypeScript
آخر تحديث: 2026-08-26
تُعد الفئات والواجهات الركيزتين الأساسيتين للبرمجة الموجهة للكائنات في TypeScript — حيث توفر الفئات عمليات التنفيذ، بينما تحدد الواجهات الشروط. وعندما تنفذ فئة ما واجهةً ما، يضمن TypeScript أن تفي تلك الفئة بجميع المتطلبات المحددة في الواجهة.
1. فئة ما تُنفِّذ واجهة (تُنفِّذ)
(1) قواعد النحو الأساسية
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) أهمية تنفيذ الواجهات
الواجهة هي «عقد» — فعندما تنفذ فئة ما واجهةً ما، فإنها تتعهد قائلةً: «سأوفر جميع الخصائص والطرق التي تتطلبها الواجهة»:
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) تنفيذ متعدد الواجهات
يمكن للفئة أن تُنفِّذ واجهات متعددة — مفصولة بفواصل:
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)
▶ مثال: تعريف واجهة برمجة تطبيقات موحدة باستخدام الواجهات
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
الناتج:
Charlie
2
2. الفئات التي ترث من الواجهات
من السمات الفريدة في 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) قواعد النحو الأساسية
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) متى تُستخدم الواجهات
- ما عليك سوى تحديد «ما يمكنه فعله» (وصف القدرات)
- قد تحتاج الفئة إلى تنفيذ قدرات متعددة (تنفيذ واجهات متعددة)
- لا داعي لمشاركة كود التنفيذ
- يلزم وجود عبارة دمج
▶ مثال: تطبيق نمط «طريقة القالب» باستخدام فئة مجردة
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"
الناتج:
HELLO WORLD
[Reversal] dlrow olleh
4. استخدام الفئات كأنواع
يُعد تعريف الفئة بحد ذاته نوعًا — يمكنك استخدام اسم الفئة كنوع لمتغير:
(1) مفهوم الفئة
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
new Point().
(2) نوع الفئة مقابل عامل typeof
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
▶ مثال: تنفيذ واجهات متعددة في فئة واحدة
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}'
الناتج:
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 {} هذا صحيح — سترث الواجهة جميع توقيعات العناصر من الفئة المجردة (بما في ذلك توقيعات العناصر الخاصة). ومع ذلك، يجب على الفئات التي تنفذ هذه الواجهة أن توفر تنفيذيها الخاصة لجميع العناصر، بما في ذلك العناصر الخاصة.📖 ملخص
implementsيتطلب أن تقوم الفئة بتنفيذ جميع عناصر واجهة البرمجة — حيث يتحقق TypeScript من ذلك في وقت التحويل البرمجي للتأكد من استيفاء هذا الشرط- يمكن للفئة أن تنفذ واجهات متعددة، مفصولة بفواصل.
- يمكن للواجهة أن تمتد من فئة ما — بحيث ترث توقيع النوع فقط، وليس التنفيذ
- توفر الفئة المجردة تنفيذًا جزئيًا؛ ويجب على الفئات الفرعية تنفيذ الطرق المجردة المتبقية.
- تُعد الفئات المجردة مناسبة لمشاركة كود التنفيذ، في حين تُعد الواجهات مناسبة لتعريف شروط القدرات؛ ويمكن استخدام الاثنين معًا.
- تعريف الفئة هو في حد ذاته نوع — ويمكن استخدامه كتعليق نوع للمتغيرات.
📝 تمارين
- المشكلة الأساسية (صعوبة ⭐): عرّف واجهة
Loggable(التي تحتوي على طريقةlog)، واجعل فئتيUserوProductتنفذانها على التوالي. أنشئ مثيلات واستدعِ الطريقةlog. - مشكلة متقدمة (درجة الصعوبة ⭐⭐): حدد فئة مجردة
Vehicle(تحتوي على خاصيةbrand، وطريقة مجردةstart، وطريقة ملموسةdescribe)، ثم أنشئ فئتين فرعيتينCarوBikeاللتين تنفذان الطريقةstart، على التوالي. اكتب دالة تأخذ معلمة من النوعVehicleوتستدعي الطريقةdescribe. - التحدي (الصعوبة: ⭐⭐⭐): صمم واجهة
SortableCollection<T>(تحتوي على خاصيةlength، وطريقةcompare، وطريقةswap)، واجعل الفئةNumberCollectionتنفذها. ثم، قم بتنفيذ خوارزمية الفرز الفقاعي في الفئة المجردةSorter(باستخدام طرق الواجهة)، بحيث لا تحتاج الفئات الفرعية سوى إلى توفير تطبيقات ملموسة لـcompareوswap.