أنماط التصميم في TypeScript: متى ولماذا
\n\nتخيل أنك تبني منزلاً. يمكنك وضع الطوب بشكل عشوائي، وعلى الأرجح لن ينهار. ولكن هل سيكون مريحاً للعيش فيه؟ على الأرجح لا. هندسة البرمجيات هي نفس البناء، ولكن بدلاً من الطوب لدينا الفئات والدوال. أنماط التصميم في TypeScript هي مخططات مجربة للمهام المعمارية النمطية. ولكن، مثل أي أداة، يجب استخدامها بحكمة.
\n\nفي هذه المقالة، لن نعيد سرد النظرية الجافة من كتب GoF (عصابة الأربعة). بدلاً من ذلك، سنحلل أي أنماط تصميم TypeScript مطلوبة حقاً في التطوير الحديث، ومتى تنقذ المشروع، ومتى تسبب الصداع فقط. هيا بنا!
\n\nلماذا يغير TypeScript قواعد اللعبة؟
\n\nقبل الغوص في الأنماط، دعنا نتذكر الميزة الرئيسية لـ TypeScript - الكتابة الثابتة (Static Typing). في JavaScript، كنا نستخدم الأنماط غالباً لتعويض الطبيعة الديناميكية للغة. على سبيل المثال، ساعد طريقة المصنع (Factory Method) في إخفاء تعقيد إنشاء الكائنات. في TypeScript، غالباً ما يتم حل هذا التعقيد ببساطة باستخدام الواجهات (Interfaces) والأدوية (Generics).
\n\nولكن هذا لا يعني أن الأنماط قد ماتت. على العكس، TypeScript يجعلها أكثر تعبيراً وأماناً. يقوم المترجم (Compiler) بالتحقق من أنك قمت بتنفيذ الهيكل بشكل صحيح، ولا يسمح لك بارتكاب أخطاء غبية. هنا يكشف TypeScript قوة أنماط GoF.
\n\nأفضل 5 أنماط ستستخدمها في أغلب الأحيان
\n\n1. Singleton (المفرد) - عندما يكون الواحد جيداً والاثنان زائدين
\n\nمتى تستخدم: تحتاج إلى نسخة واحدة بالضبط من الفئة للتطبيق بأكمله. الأمثلة الكلاسيكية هي المسجل (Logger)، إعدادات التطبيق، الاتصال بقاعدة البيانات.
\n\nلماذا في TypeScript: TypeScript يسمح بجعل Singleton آمناً للخيوط (Thread-safe) وآمناً للأنواع (Type-safe). بدونه، تخاطر بتوليد عشرات الكائنات التي ستستهلك الذاكرة وتخلط الحالة.
\n\nمثال كود:
\n\nclass DatabaseConnection {\n private static instance: DatabaseConnection;\n private connectionString: string;\n\n private constructor(connectionString: string) {\n this.connectionString = connectionString;\n // محاكاة الاتصال\n console.log(`اتصال بـ ${connectionString}`);\n }\n\n static getInstance(connectionString: string): DatabaseConnection {\n if (!DatabaseConnection.instance) {\n DatabaseConnection.instance = new DatabaseConnection(connectionString);\n }\n return DatabaseConnection.instance;\n }\n\n query(sql: string): void {\n console.log(`تنفيذ الاستعلام: ${sql}`);\n }\n}\n\n// الاستخدام\nconst db = DatabaseConnection.getInstance('postgres://localhost:5432/mydb');\ndb.query('SELECT * FROM users');\n\n// محاولة إنشاء اتصال آخر سيعيد نفس النسخة\nconst db2 = DatabaseConnection.getInstance('postgres://localhost:5432/mydb');\nconsole.log(db === db2); // true\n\nالنمط المضاد: لا تجعل Singleton من أجل Singleton. إذا كان الكائن يمكن أن يوجد في عدة نسخ بحالات مختلفة (مثل اتصالات مختلفة بقواعد بيانات مختلفة)، فإن Singleton هو شر.
\n\n2. Factory Method (طريقة المصنع) - إنشاء الكائنات بدون ألم
\n\nمتى تستخدم: لا تعرف مسبقاً أي كائن بالضبط تحتاج إلى إنشائه. أو أن عملية الإنشاء معقدة وتتطلب منطقاً.
\n\nلماذا في TypeScript: طريقة المصنع تتناسب بشكل ممتاز مع الواجهات والأدوية. تحصل على مرونة دون فقدان الأنواع.
\n\nمثال كود:
\n\n// منتج مجرد\ninterface Notification {\n send(message: string): void;\n}\n\n// منتجات محددة\nclass EmailNotification implements Notification {\n send(message: string): void {\n console.log(`إرسال بريد إلكتروني: ${message}`);\n }