لغة TypeScript

أنماط التصميم في تايب سكريبت

40 دقيقة الدرس 23 من 40

أنماط التصميم في تايب سكريبت

أنماط التصميم هي حلول مثبتة لمشاكل تصميم البرمجيات الشائعة. نظام الأنواع في تايب سكريبت يجعل تنفيذ هذه الأنماط أكثر أماناً وتعبيراً من جافا سكريبت العادية. في هذا الدرس، سنستكشف أنماط التصميم الكلاسيكية—Singleton، وFactory، وObserver، وStrategy—ونرى كيف تعزز ميزات تايب سكريبت تنفيذها.

نمط Singleton

نمط Singleton يضمن أن الفئة لها نسخة واحدة فقط ويوفر نقطة وصول عالمية لها:

// تنفيذ Singleton الأساسي class DatabaseConnection { private static instance: DatabaseConnection; private connected: boolean = false; // البناء الخاص يمنع الإنشاء المباشر private constructor( private readonly connectionString: string ) {} static getInstance(connectionString: string): DatabaseConnection { if (!DatabaseConnection.instance) { DatabaseConnection.instance = new DatabaseConnection(connectionString); } return DatabaseConnection.instance; } connect(): void { if (!this.connected) { console.log(\`الاتصال بـ ${this.connectionString}\`); this.connected = true; } } query(sql: string): void { if (!this.connected) { throw new Error('غير متصل بقاعدة البيانات'); } console.log(\`تنفيذ: ${sql}\`); } } // الاستخدام const db1 = DatabaseConnection.getInstance('postgres://localhost'); const db2 = DatabaseConnection.getInstance('mysql://localhost'); console.log(db1 === db2); // true - نفس النسخة // نمط Singleton عام class Singleton<T> { private static instances = new Map<Function, unknown>(); protected constructor() {} static getInstance<T extends Singleton<any>>( this: new () => T ): T { if (!Singleton.instances.has(this)) { Singleton.instances.set(this, new this()); } return Singleton.instances.get(this) as T; } } // استخدام Singleton العام class Logger extends Singleton<Logger> { private logs: string[] = []; log(message: string): void { this.logs.push(\`[${new Date().toISOString()}] ${message}\`); } getLogs(): string[] { return [...this.logs]; } } const logger1 = Logger.getInstance(); const logger2 = Logger.getInstance(); console.log(logger1 === logger2); // true // Singleton مع التهيئة الكسولة وأمان الخيط class ConfigManager { private static instance: ConfigManager | null = null; private static isCreating = false; private config: Map<string, unknown> = new Map(); private constructor() {} static async getInstance(): Promise<ConfigManager> { if (ConfigManager.instance) { return ConfigManager.instance; } // منع حالات السباق while (ConfigManager.isCreating) { await new Promise(resolve => setTimeout(resolve, 10)); } if (!ConfigManager.instance) { ConfigManager.isCreating = true; ConfigManager.instance = new ConfigManager(); await ConfigManager.instance.loadConfig(); ConfigManager.isCreating = false; } return ConfigManager.instance; } private async loadConfig(): Promise<void> { // محاكاة تحميل التكوين غير المتزامن const response = await fetch('/api/config'); const data = await response.json(); Object.entries(data).forEach(([key, value]) => { this.config.set(key, value); }); } get<T>(key: string): T | undefined { return this.config.get(key) as T | undefined; } }
ملاحظة: نمط Singleton مفيد لإدارة الموارد المشتركة مثل اتصالات قاعدة البيانات أو التكوين أو التسجيل. ومع ذلك، استخدمه بحذر لأنه يقدم حالة عالمية.

نمط Factory

نمط Factory يوفر واجهة لإنشاء الكائنات دون تحديد فئاتها الدقيقة:

// واجهة المنتج interface Vehicle { readonly type: string; start(): void; stop(): void; getInfo(): string; } // المنتجات الملموسة class Car implements Vehicle { readonly type = 'car'; constructor( private readonly model: string, private readonly year: number ) {} start(): void { console.log(\`بدء تشغيل سيارة ${this.model}\`); } stop(): void { console.log('توقفت السيارة'); } getInfo(): string { return \`${this.year} ${this.model}\`; } } class Motorcycle implements Vehicle { readonly type = 'motorcycle'; constructor( private readonly brand: string, private readonly cc: number ) {} start(): void { console.log(\`بدء تشغيل دراجة ${this.brand}\`); } stop(): void { console.log('توقفت الدراجة'); } getInfo(): string { return \`${this.brand} ${this.cc}cc\`; } } class Truck implements Vehicle { readonly type = 'truck'; constructor( private readonly capacity: number ) {} start(): void { console.log('بدء تشغيل الشاحنة'); } stop(): void { console.log('توقفت الشاحنة'); } getInfo(): string { return \`شاحنة (سعة ${this.capacity} طن)\`; } } // فئة Factory class VehicleFactory { static createVehicle(type: 'car', model: string, year: number): Car; static createVehicle(type: 'motorcycle', brand: string, cc: number): Motorcycle; static createVehicle(type: 'truck', capacity: number): Truck; static createVehicle( type: string, ...args: unknown[] ): Vehicle { switch (type) { case 'car': return new Car(args[0] as string, args[1] as number); case 'motorcycle': return new Motorcycle(args[0] as string, args[1] as number); case 'truck': return new Truck(args[0] as number); default: throw new Error(\`نوع مركبة غير معروف: ${type}\`); } } } // الاستخدام مع أمان النوع const car = VehicleFactory.createVehicle('car', 'Tesla Model 3', 2023); const bike = VehicleFactory.createVehicle('motorcycle', 'Harley', 1200); const truck = VehicleFactory.createVehicle('truck', 10); // نمط Abstract Factory مع العموميات interface UIComponent { render(): string; } class Button implements UIComponent { constructor(private readonly theme: string) {} render(): string { return \`<button class="${this.theme}-button">انقر</button>\`; } } class Input implements UIComponent { constructor(private readonly theme: string) {} render(): string { return \`<input class="${this.theme}-input" />\`; } } abstract class UIFactory { abstract createButton(): Button; abstract createInput(): Input; createComponents(): UIComponent[] { return [this.createButton(), this.createInput()]; } } class LightThemeFactory extends UIFactory { createButton(): Button { return new Button('light'); } createInput(): Input { return new Input('light'); } } class DarkThemeFactory extends UIFactory { createButton(): Button { return new Button('dark'); } createInput(): Input { return new Input('dark'); } } // Factory مع بناة آمنة من حيث النوع type VehicleConfig = { car: { model: string; year: number }; motorcycle: { brand: string; cc: number }; truck: { capacity: number }; }; class TypeSafeVehicleFactory { static create<T extends keyof VehicleConfig>( type: T, config: VehicleConfig[T] ): Vehicle { switch (type) { case 'car': const carConfig = config as VehicleConfig['car']; return new Car(carConfig.model, carConfig.year); case 'motorcycle': const bikeConfig = config as VehicleConfig['motorcycle']; return new Motorcycle(bikeConfig.brand, bikeConfig.cc); case 'truck': const truckConfig = config as VehicleConfig['truck']; return new Truck(truckConfig.capacity); default: throw new Error('نوع غير معروف'); } } } // استخدام آمن من حيث النوع const myCar = TypeSafeVehicleFactory.create('car', { model: 'Tesla', year: 2023 });
نصيحة: استخدم تحميل الدالة الزائد مع نمط Factory لتوفير كتابة قوية لأنواع منتجات مختلفة مع الحفاظ على واجهة موحدة.

نمط Observer

نمط Observer يحدد تبعية واحد لكثير بين الكائنات بحيث عندما يتغير كائن واحد الحالة، يتم إخطار جميع المعتمدين عليه:

// نمط Observer آمن من حيث النوع type EventMap = Record<string, unknown>; interface Observer<T> { update(data: T): void; } class Subject<T extends EventMap> { private observers = new Map<keyof T, Set<Observer<T[keyof T]>>>(); subscribe<K extends keyof T>( event: K, observer: Observer<T[K]> ): () => void { if (!this.observers.has(event)) { this.observers.set(event, new Set()); } this.observers.get(event)!.add(observer as Observer<T[keyof T]>); // إرجاع دالة إلغاء الاشتراك return () => { this.observers.get(event)?.delete(observer as Observer<T[keyof T]>); }; } notify<K extends keyof T>(event: K, data: T[K]): void { const observers = this.observers.get(event); if (observers) { observers.forEach(observer => observer.update(data)); } } unsubscribeAll(event?: keyof T): void { if (event) { this.observers.delete(event); } else { this.observers.clear(); } } } // تحديد أنواع الأحداث type UserEvents = { login: { userId: number; timestamp: Date }; logout: { userId: number }; update: { userId: number; fields: string[] }; }; // إنشاء الموضوع const userSubject = new Subject<UserEvents>(); // إنشاء المراقبين const loginLogger: Observer<UserEvents['login']> = { update(data) { console.log(\`المستخدم ${data.userId} سجل الدخول في ${data.timestamp}\`); } }; const logoutLogger: Observer<UserEvents['logout']> = { update(data) { console.log(\`المستخدم ${data.userId} سجل الخروج\`); } }; // الاشتراك const unsubscribeLogin = userSubject.subscribe('login', loginLogger); userSubject.subscribe('logout', logoutLogger); // الإخطار userSubject.notify('login', { userId: 1, timestamp: new Date() }); userSubject.notify('logout', { userId: 1 }); // إلغاء الاشتراك unsubscribeLogin(); // EventEmitter حديث مع تايب سكريبت class EventEmitter<T extends EventMap> { private listeners = new Map<keyof T, Set<(data: T[keyof T]) => void>>(); on<K extends keyof T>(event: K, listener: (data: T[K]) => void): this { if (!this.listeners.has(event)) { this.listeners.set(event, new Set()); } this.listeners.get(event)!.add(listener as (data: T[keyof T]) => void); return this; } off<K extends keyof T>(event: K, listener: (data: T[K]) => void): this { this.listeners.get(event)?.delete(listener as (data: T[keyof T]) => void); return this; } emit<K extends keyof T>(event: K, data: T[K]): boolean { const listeners = this.listeners.get(event); if (!listeners || listeners.size === 0) return false; listeners.forEach(listener => { try { listener(data); } catch (error) { console.error(\`خطأ في المستمع لـ ${String(event)}:\`, error); } }); return true; } once<K extends keyof T>(event: K, listener: (data: T[K]) => void): this { const onceWrapper = (data: T[K]) => { listener(data); this.off(event, onceWrapper); }; return this.on(event, onceWrapper); } } // الاستخدام type AppEvents = { 'user:created': { id: number; name: string }; 'user:deleted': { id: number }; error: { message: string; code: number }; }; const emitter = new EventEmitter<AppEvents>(); emitter.on('user:created', (data) => { console.log(\`مستخدم جديد: ${data.name} (${data.id})\`); }); emitter.once('error', (data) => { console.log(\`خطأ ${data.code}: ${data.message}\`); }); emitter.emit('user:created', { id: 1, name: 'أحمد' }); emitter.emit('error', { message: 'فشل الاتصال', code: 500 });
ملاحظة: نمط Observer أساسي للبنى المعمارية القائمة على الأحداث والبرمجة التفاعلية. عموميات تايب سكريبت تضمن أمان النوع لجميع الأحداث وبياناتها.

نمط Strategy

نمط Strategy يحدد عائلة من الخوارزميات، ويغلف كل واحدة، ويجعلها قابلة للتبديل:

// واجهة الاستراتيجية interface SortStrategy<T> { sort(data: T[]): T[]; } // الاستراتيجيات الملموسة class BubbleSort<T> implements SortStrategy<T> { sort(data: T[]): T[] { const result = [...data]; for (let i = 0; i < result.length; i++) { for (let j = 0; j < result.length - 1 - i; j++) { if (result[j] > result[j + 1]) { [result[j], result[j + 1]] = [result[j + 1], result[j]]; } } } return result; } } class QuickSort<T> implements SortStrategy<T> { sort(data: T[]): T[] { if (data.length <= 1) return data; const pivot = data[Math.floor(data.length / 2)]; const left = data.filter(x => x < pivot); const middle = data.filter(x => x === pivot); const right = data.filter(x => x > pivot); return [...this.sort(left), ...middle, ...this.sort(right)]; } } // السياق class Sorter<T> { constructor(private strategy: SortStrategy<T>) {} setStrategy(strategy: SortStrategy<T>): void { this.strategy = strategy; } sort(data: T[]): T[] { return this.strategy.sort(data); } } // الاستخدام const numbers = [5, 2, 8, 1, 9]; const sorter = new Sorter(new BubbleSort<number>()); console.log(sorter.sort(numbers)); sorter.setStrategy(new QuickSort<number>()); console.log(sorter.sort(numbers)); // استراتيجية متقدمة مع أنواع الدوال type ValidationRule<T> = (value: T) => boolean | string; class Validator<T> { private rules: ValidationRule<T>[] = []; addRule(rule: ValidationRule<T>): this { this.rules.push(rule); return this; } validate(value: T): { valid: boolean; errors: string[] } { const errors: string[] = []; for (const rule of this.rules) { const result = rule(value); if (result === false) { errors.push('فشل التحقق'); } else if (typeof result === 'string') { errors.push(result); } } return { valid: errors.length === 0, errors }; } } // الاستخدام const emailValidator = new Validator<string>() .addRule(value => value.length > 0 || 'البريد الإلكتروني مطلوب') .addRule(value => value.includes('@') || 'تنسيق البريد الإلكتروني غير صالح') .addRule(value => value.length <= 100 || 'البريد الإلكتروني طويل جداً'); console.log(emailValidator.validate('test@example.com')); console.log(emailValidator.validate('invalid')); // الاستراتيجية مع حقن التبعية interface PaymentStrategy { pay(amount: number): Promise<{ success: boolean; transactionId?: string }>; } class CreditCardPayment implements PaymentStrategy { constructor( private readonly cardNumber: string, private readonly cvv: string ) {} async pay(amount: number): Promise<{ success: boolean; transactionId?: string }> { console.log(\`معالجة دفع بطاقة الائتمان بقيمة $${amount}\`); // محاكاة معالجة الدفع return { success: true, transactionId: 'CC-' + Date.now() }; } } class PayPalPayment implements PaymentStrategy { constructor(private readonly email: string) {} async pay(amount: number): Promise<{ success: boolean; transactionId?: string }> { console.log(\`معالجة دفع PayPal بقيمة $${amount}\`); return { success: true, transactionId: 'PP-' + Date.now() }; } } class CryptoPayment implements PaymentStrategy { constructor(private readonly walletAddress: string) {} async pay(amount: number): Promise<{ success: boolean; transactionId?: string }> { console.log(\`معالجة دفع العملات المشفرة بقيمة $${amount}\`); return { success: true, transactionId: 'CR-' + Date.now() }; } } class PaymentProcessor { constructor(private strategy: PaymentStrategy) {} setStrategy(strategy: PaymentStrategy): void { this.strategy = strategy; } async processPayment(amount: number): Promise<void> { const result = await this.strategy.pay(amount); if (result.success) { console.log(\`نجح الدفع: ${result.transactionId}\`); } else { console.log('فشل الدفع'); } } } // الاستخدام const processor = new PaymentProcessor( new CreditCardPayment('1234-5678-9012-3456', '123') ); await processor.processPayment(100); processor.setStrategy(new PayPalPayment('user@example.com')); await processor.processPayment(50);
تحذير: بينما أنماط التصميم قوية، لا تجبرها في كودك. استخدم الأنماط عندما تحل مشكلة بشكل طبيعي، وليس فقط من أجل استخدامها.
تمرين:
  1. نفذ نمط Builder لإنشاء كائنات تكوين معقدة مع تايب سكريبت، مع ضمان أمان النوع للحقول المطلوبة والاختيارية.
  2. أنشئ تنفيذ نمط Decorator لإضافة التسجيل والتخزين المؤقت والتحقق إلى طرق الفئة باستخدام decorators تايب سكريبت.
  3. ابنِ نظام نمط Command لتنفيذ وظيفة التراجع/الإعادة مع كتابة صحيحة لأنواع الأوامر المختلفة.
  4. نفذ نمط Adapter لإنشاء واجهة موحدة لواجهات برمجة تطبيقات طرف ثالث متعددة بهياكل مختلفة.
  5. أنشئ نمط Chain of Responsibility لمعالجة الطلبات مع تسجيل معالج آمن من حيث النوع وتمرير البيانات.

الخلاصة

تايب سكريبت تعزز أنماط التصميم الكلاسيكية بكتابة قوية، مما يجعلها أكثر أماناً وتعبيراً. نمط Singleton يدير الموارد المشتركة، وFactory ينشئ الكائنات بمرونة، وObserver ينفذ البنى المعمارية القائمة على الأحداث، وStrategy يغلف الخوارزميات القابلة للتبديل. من خلال الاستفادة من العموميات والواجهات واستنتاج الأنواع في تايب سكريبت، يمكنك تنفيذ هذه الأنماط بأمان وقت الترجمة الذي يمنع العديد من أخطاء وقت التشغيل.