أنماط التصميم في JavaScript مع أمثلة
أنماط التصميم (design patterns) هي حلول مجربة للمشكلات النمطية في تطوير البرمجيات. بالنسبة لمطور JavaScript، فإن معرفة الأنماط مهمة بشكل خاص، لأن اللغة تمتلك نموذجًا كائنيًا مرنًا وتستخدم على نطاق واسع سواء على العميل أو الخادم (Node.js). في هذه المقالة، سنشرح أربعة أنماط رئيسية مع أمثلة عملية بلغة JavaScript.
لماذا يجب دراسة أنماط التصميم؟
تساعد الأنماط في:
- تبسيط صيانة الكود وتوسيعه
- جعل الكود أكثر قابلية للقراءة والتوقع
- تجنب "إعادة اختراع العجلة"
- تحسين التواصل داخل الفريق (لغة مشتركة)
1. Singleton (المفرد)
يضمن نمط Singleton أن يكون للفصل نسخة واحدة فقط، ويوفر نقطة وصول عامة إليها. في Node.js، يُستخدم هذا النمط غالبًا للاتصال بقاعدة البيانات، أو العمل مع الإعدادات، أو تسجيل السجلات.
class DatabaseConnection { constructor() { if (DatabaseConnection.instance) { return DatabaseConnection.instance; } this.connection = null; DatabaseConnection.instance = this; }
connect() { // محاكاة الاتصال بقاعدة البيانات this.connection = "connected"; console.log("تم إنشاء الاتصال بقاعدة البيانات"); }
query(sql) { if (!this.connection) { throw new Error("لا يوجد اتصال بقاعدة البيانات"); } console.log(`تنفيذ الاستعلام: ${sql}`); return { data: [] }; }}
// الاستخدامconst db1 = new DatabaseConnection();const db2 = new DatabaseConnection();
db1.connect();console.log(db1 === db2); // true - كلاهما يشير إلى نفس النسخةdb2.query("SELECT * FROM users");متى يُستخدم: إدارة الموارد المشتركة (قاعدة البيانات، الإعدادات، التخزين المؤقت). تنبيه: الاستخدام المفرط قد يؤدي إلى ارتباط قوي في الكود.
2. Factory (المصنع)
يحدد Factory Method واجهة لإنشاء كائن، لكنه يسمح للفئات الفرعية بتغيير نوع الكائن الذي يتم إنشاؤه. في JavaScript، غالبًا ما يتم تنفيذ ذلك من خلال الدوال العادية أو الفئات.
class User { constructor(name, role) { this.name = name; this.role = role; }
getPermissions() { return []; }}
class Admin extends User { getPermissions() { return ["read", "write", "delete", "manage"]; }}
class Editor extends User { getPermissions() { return ["read", "write"]; }}
class Viewer extends User { getPermissions() { return ["read"]; }}
// المصنعclass UserFactory { createUser(name, role) { switch (role) { case "admin": return new Admin(name, role); case "editor": return new Editor(name, role); case "viewer": return new Viewer(name, role); default: throw new Error("دور غير معروف"); } }}
// الاستخدامconst factory = new UserFactory();const admin = factory.createUser("آنا", "admin");const viewer = factory.createUser("إيفان", "viewer");
console.log(admin.getPermissions()); // ["read", "write", "delete", "manage"]console.log(viewer.getPermissions()); // ["read"]المزايا: إدارة مركزية لإنشاء الكائنات، سهولة إضافة أنواع جديدة، إخفاء منطق الإنشاء.
3. Observer (المراقب)
يحدد نمط Observer علاقة "واحد إلى متعدد" بين الكائنات. عندما يغير كائن (الموضوع) حالته، يتم إخطار جميع الكائنات التابعة (المراقبين) تلقائيًا. هذا هو أساس البرمجة الموجهة بالأحداث في Node.js.
class EventEmitter { constructor() { this.events = {}; }
on(eventName, listener) { if (!this.events[eventName]) { this.events[eventName] = []; } this.events[eventName].push(listener); }
emit(eventName, ...args) { const listeners = this.events[eventName]; if (listeners) { listeners.forEach(listener => listener(...args)); } }}
// الاستخدامconst emitter = new EventEmitter();
emitter.on('userLogin', (username) => { console.log(`تسجيل الدخول: ${username}`);});
emitter.on('userLogin', (username) => { // إرسال البريد الإلكتروني console.log(`تم إرسال إشعار إلى ${username}`);});
emitter.emit('userLogin', 'أحمد');// الإخراج:// تسجيل الدخول: أحمد// تم إرسال إشعار إلى أحمدمتى يُستخدم: معالجة الأحداث، تحديث واجهة المستخدم، الأنظمة غير المتزامنة. تنبيه: قد يؤدي إلى تسرب الذاكرة إذا لم يتم إلغاء الاشتراك بشكل صحيح.
4. Module (الوحدة النمطية)
نمط Module يغلف الكود الخاص، ويكشف فقط عن الواجهة العامة. في JavaScript الحديث، يتم تنفيذ ذلك باستخدام import/export، لكن النمط الكلاسيكي يستخدم Immediately Invoked Function Expression (IIFE).
const UserModule = (function() { // متغير خاص let users = []; // دالة خاصة function validateUser(user) { return user.name && user.email; } // واجهة عامة return { addUser(user) { if (validateUser(user)) { users.push(user); console.log(`تمت إضافة المستخدم: ${user.name}`); } else { console.log('بيانات المستخدم غير صالحة'); } }, getUsers() { return [...users]; }, getUserCount() { return users.length; } };})();
// الاستخدامUserModule.addUser({ name: 'مريم', email: 'mariam@example.com' });UserModule.addUser({ name: 'خالد' }); // بيانات غير صالحة
console.log(UserModule.getUsers());console.log(`عدد المستخدمين: ${UserModule.getUserCount()}`);
// console.log(UserModule.users); // undefined - خاصالمزايا: تغليف الكود، منع التلوث العام، إنشاء واجهة برمجة تطبيقات نظيفة. ملاحظة: في التطبيقات الحديثة، يُفضل استخدام ES6 Modules.
الخلاصة
أنماط التصميم ليست مجرد حلول جاهزة، بل هي طريقة في التفكير تساعد في إنشاء كود أكثر مرونة وقابلية للصيانة. في JavaScript، تكتسب هذه الأنماط خصائصها الخاصة بسبب طبيعة اللغة الديناميكية. ابدأ بتطبيق الأنماط البسيطة مثل Module وSingleton، ثم انتقل تدريجيًا إلى الأنماط الأكثر تعقيدًا مثل Observer وFactory. تذكر أن الهدف ليس تطبيق الأنماط في كل مكان، بل استخدامها عند الحاجة الحقيقية.