فارسی

با راهنمای کامل پیاده‌سازی ما، بر الگوهای طراحی جاوا اسکریپت مسلط شوید. الگوهای ایجادی، ساختاری و رفتاری را با مثال‌های کد عملی بیاموزید.

الگوهای طراحی جاوا اسکریپت: راهنمای جامع پیاده‌سازی برای توسعه‌دهندگان مدرن

مقدمه: طرح اولیه برای کدی استوار

در دنیای پویای توسعه نرم‌افزار، نوشتن کدی که صرفاً کار کند تنها قدم اول است. چالش واقعی، و نشانه یک توسعه‌دهنده حرفه‌ای، ایجاد کدی است که مقیاس‌پذیر، قابل نگهداری و درک و همکاری بر روی آن برای دیگران آسان باشد. اینجاست که الگوهای طراحی وارد می‌شوند. آنها الگوریتم‌ها یا کتابخانه‌های خاصی نیستند، بلکه طرح‌های سطح بالا و مستقل از زبانی برای حل مشکلات تکرارشونده در معماری نرم‌افزار هستند.

برای توسعه‌دهندگان جاوا اسکریپت، درک و به کارگیری الگوهای طراحی بیش از هر زمان دیگری حیاتی است. با افزایش پیچیدگی برنامه‌ها، از فریم‌ورک‌های پیچیده فرانت‌اند گرفته تا سرویس‌های قدرتمند بک‌اند بر روی Node.js، داشتن یک زیربنای معماری مستحکم غیرقابل‌مذاکره است. الگوهای طراحی این زیربنا را فراهم می‌کنند و راه‌حل‌های آزموده‌شده‌ای را ارائه می‌دهند که اتصال سست (loose coupling)، تفکیک دغدغه‌ها (separation of concerns) و قابلیت استفاده مجدد کد را ترویج می‌کنند.

این راهنمای جامع شما را با سه دسته اساسی الگوهای طراحی آشنا می‌کند و توضیحات واضح و مثال‌های پیاده‌سازی عملی و مدرن جاوا اسکریپت (ES6+) را ارائه می‌دهد. هدف ما این است که شما را به دانشی مجهز کنیم تا بتوانید تشخیص دهید کدام الگو برای یک مسئله مشخص مناسب است و چگونه آن را به طور مؤثر در پروژه‌های خود پیاده‌سازی کنید.

سه ستون اصلی الگوهای طراحی

الگوهای طراحی معمولاً به سه گروه اصلی دسته‌بندی می‌شوند که هر کدام به مجموعه متمایزی از چالش‌های معماری می‌پردازند:

بیایید با مثال‌های عملی به هر دسته بپردازیم.


الگوهای ایجادی: تسلط بر ایجاد شیء

الگوهای ایجادی مکانیسم‌های مختلفی برای ایجاد شیء فراهم می‌کنند که انعطاف‌پذیری و استفاده مجدد از کد موجود را افزایش می‌دهند. آنها به جداسازی یک سیستم از نحوه ایجاد، ترکیب و نمایش اشیاء آن کمک می‌کنند.

الگوی سینگلتون (The Singleton Pattern)

مفهوم: الگوی سینگلتون تضمین می‌کند که یک کلاس تنها یک نمونه (instance) داشته باشد و یک نقطه دسترسی سراسری و واحد به آن فراهم می‌کند. هر تلاشی برای ایجاد یک نمونه جدید، نمونه اصلی را برمی‌گرداند.

موارد استفاده رایج: این الگو برای مدیریت منابع یا وضعیت اشتراکی مفید است. مثال‌ها شامل یک استخر اتصال پایگاه داده واحد، یک مدیر پیکربندی سراسری، یا یک سرویس لاگ‌گیری است که باید در کل برنامه یکپارچه باشد.

پیاده‌سازی در جاوا اسکریپت: جاوا اسکریپت مدرن، به ویژه با کلاس‌های ES6، پیاده‌سازی سینگلتون را ساده می‌کند. می‌توانیم از یک ویژگی استاتیک در کلاس برای نگهداری نمونه واحد استفاده کنیم.

مثال: یک سرویس لاگ‌گیری سینگلتون

class Logger { constructor() { if (Logger.instance) { return Logger.instance; } this.logs = []; Logger.instance = this; } log(message) { const timestamp = new Date().toISOString(); this.logs.push({ message, timestamp }); console.log(`${timestamp} - ${message}`); } getLogCount() { return this.logs.length; } } // کلمه کلیدی 'new' فراخوانی می‌شود، اما منطق سازنده یک نمونه واحد را تضمین می‌کند. const logger1 = new Logger(); const logger2 = new Logger(); console.log("آیا لاگرها یک نمونه یکسان هستند؟", logger1 === logger2); // true logger1.log("پیام اول از logger1."); logger2.log("پیام دوم از logger2."); console.log("تعداد کل لاگ‌ها:", logger1.getLogCount()); // 2

مزایا و معایب:

الگوی فکتوری (The Factory Pattern)

مفهوم: الگوی فکتوری یک رابط برای ایجاد اشیاء در یک کلاس والد (superclass) فراهم می‌کند، اما به زیرکلاس‌ها اجازه می‌دهد تا نوع اشیائی که ایجاد خواهند شد را تغییر دهند. این الگو به استفاده از یک متد یا کلاس اختصاصی "فکتوری" برای ایجاد اشیاء بدون مشخص کردن کلاس‌های واقعی آنها می‌پردازد.

موارد استفاده رایج: زمانی که شما کلاسی دارید که نمی‌تواند نوع اشیائی که باید ایجاد کند را پیش‌بینی کند، یا زمانی که می‌خواهید به کاربران کتابخانه خود راهی برای ایجاد اشیاء بدهید بدون اینکه آنها نیاز به دانستن جزئیات پیاده‌سازی داخلی داشته باشند. یک مثال رایج، ایجاد انواع مختلف کاربران (Admin, Member, Guest) بر اساس یک پارامتر است.

پیاده‌سازی در جاوا اسکریپت:

مثال: یک فکتوری کاربر (User Factory)

class RegularUser { constructor(name) { this.name = name; this.role = 'Regular'; } viewDashboard() { console.log(`${this.name} در حال مشاهده داشبورد کاربر است.`); } } class AdminUser { constructor(name) { this.name = name; this.role = 'Admin'; } viewDashboard() { console.log(`${this.name} در حال مشاهده داشبورد ادمین با دسترسی کامل است.`); } } class UserFactory { static createUser(type, name) { switch (type.toLowerCase()) { case 'admin': return new AdminUser(name); case 'regular': return new RegularUser(name); default: throw new Error('نوع کاربر نامعتبر مشخص شده است.'); } } } const admin = UserFactory.createUser('admin', 'Alice'); const regularUser = UserFactory.createUser('regular', 'Bob'); admin.viewDashboard(); // Alice در حال مشاهده داشبورد ادمین... regularUser.viewDashboard(); // Bob در حال مشاهده داشبورد کاربر. console.log(admin.role); // Admin console.log(regularUser.role); // Regular

مزایا و معایب:

الگوی پروتوتایپ (The Prototype Pattern)

مفهوم: الگوی پروتوتایپ در مورد ایجاد اشیاء جدید با کپی کردن یک شیء موجود، که به عنوان "پروتوتایپ" شناخته می‌شود، است. به جای ساختن یک شیء از ابتدا، شما یک کلون از یک شیء از پیش پیکربندی شده ایجاد می‌کنید. این امر در نحوه کار خود جاوا اسکریپت از طریق وراثت پروتوتایپی (prototypal inheritance) اساسی است.

موارد استفاده رایج: این الگو زمانی مفید است که هزینه ایجاد یک شیء گران‌تر یا پیچیده‌تر از کپی کردن یک شیء موجود باشد. همچنین برای ایجاد اشیائی که نوع آنها در زمان اجرا مشخص می‌شود، استفاده می‌شود.

پیاده‌سازی در جاوا اسکریپت: جاوا اسکریپت پشتیبانی داخلی از این الگو را از طریق `Object.create()` دارد.

مثال: پروتوتایپ وسیله نقلیه قابل کلون‌سازی

const vehiclePrototype = { init: function(model) { this.model = model; }, getModel: function() { return `مدل این وسیله نقلیه ${this.model} است`; } }; // ایجاد یک شیء خودروی جدید بر اساس پروتوتایپ وسیله نقلیه const car = Object.create(vehiclePrototype); car.init('Ford Mustang'); console.log(car.getModel()); // مدل این وسیله نقلیه Ford Mustang است // ایجاد یک شیء دیگر، یک کامیون const truck = Object.create(vehiclePrototype); truck.init('Tesla Cybertruck'); console.log(truck.getModel()); // مدل این وسیله نقلیه Tesla Cybertruck است

مزایا و معایب:


الگوهای ساختاری: مونتاژ هوشمندانه کد

الگوهای ساختاری در مورد این هستند که چگونه اشیاء و کلاس‌ها می‌توانند برای تشکیل ساختارهای بزرگ‌تر و پیچیده‌تر ترکیب شوند. آنها بر ساده‌سازی ساختار و شناسایی روابط تمرکز دارند.

الگوی آداپتور (The Adapter Pattern)

مفهوم: الگوی آداپتور به عنوان پلی بین دو رابط ناسازگار عمل می‌کند. این الگو شامل یک کلاس واحد (آداپتور) است که عملکردهای رابط‌های مستقل یا ناسازگار را به هم متصل می‌کند. آن را مانند یک آداپتور برق در نظر بگیرید که به شما امکان می‌دهد دستگاه خود را به یک پریز برق خارجی وصل کنید.

موارد استفاده رایج: ادغام یک کتابخانه شخص ثالث جدید با یک برنامه موجود که انتظار یک API متفاوت را دارد، یا وادار کردن کد قدیمی (legacy) به کار با یک سیستم مدرن بدون بازنویسی کد قدیمی.

پیاده‌سازی در جاوا اسکریپت:

مثال: تطبیق یک API جدید با یک رابط قدیمی

// رابط قدیمی و موجودی که برنامه ما استفاده می‌کند class OldCalculator { operation(term1, term2, operation) { switch (operation) { case 'add': return term1 + term2; case 'sub': return term1 - term2; default: return NaN; } } } // کتابخانه جدید و درخشان با یک رابط متفاوت class NewCalculator { add(term1, term2) { return term1 + term2; } subtract(term1, term2) { return term1 - term2; } } // کلاس آداپتور class CalculatorAdapter { constructor() { this.calculator = new NewCalculator(); } operation(term1, term2, operation) { switch (operation) { case 'add': // تطبیق فراخوانی با رابط جدید return this.calculator.add(term1, term2); case 'sub': return this.calculator.subtract(term1, term2); default: return NaN; } } } // کد کلاینت اکنون می‌تواند از آداپتور طوری استفاده کند که انگار ماشین حساب قدیمی است const oldCalc = new OldCalculator(); console.log("نتیجه ماشین حساب قدیمی:", oldCalc.operation(10, 5, 'add')); // 15 const adaptedCalc = new CalculatorAdapter(); console.log("نتیجه ماشین حساب تطبیق‌یافته:", adaptedCalc.operation(10, 5, 'add')); // 15

مزایا و معایب:

الگوی دکوراتور (The Decorator Pattern)

مفهوم: الگوی دکوراتور به شما اجازه می‌دهد تا رفتارها یا مسئولیت‌های جدیدی را به صورت پویا به یک شیء متصل کنید بدون اینکه کد اصلی آن را تغییر دهید. این کار با پیچیدن شیء اصلی در یک شیء "دکوراتور" ویژه که حاوی عملکرد جدید است، انجام می‌شود.

موارد استفاده رایج: افزودن ویژگی‌ها به یک کامپوننت UI، تقویت یک شیء کاربر با مجوزها، یا افزودن رفتار لاگ‌گیری/کش‌کردن به یک سرویس. این یک جایگزین انعطاف‌پذیر برای زیرکلاس‌سازی است.

پیاده‌سازی در جاوا اسکریپت: توابع در جاوا اسکریپت شهروندان درجه اول هستند، که پیاده‌سازی دکوراتورها را آسان می‌کند.

مثال: تزئین یک سفارش قهوه

// کامپوننت پایه class SimpleCoffee { getCost() { return 10; } getDescription() { return 'قهوه ساده'; } } // دکوراتور ۱: شیر function MilkDecorator(coffee) { const originalCost = coffee.getCost(); const originalDescription = coffee.getDescription(); coffee.getCost = function() { return originalCost + 2; }; coffee.getDescription = function() { return `${originalDescription}, با شیر`; }; return coffee; } // دکوراتور ۲: شکر function SugarDecorator(coffee) { const originalCost = coffee.getCost(); const originalDescription = coffee.getDescription(); coffee.getCost = function() { return originalCost + 1; }; coffee.getDescription = function() { return `${originalDescription}, با شکر`; }; return coffee; } // بیایید یک قهوه بسازیم و آن را تزئین کنیم let myCoffee = new SimpleCoffee(); console.log(myCoffee.getCost(), myCoffee.getDescription()); // 10, قهوه ساده myCoffee = MilkDecorator(myCoffee); console.log(myCoffee.getCost(), myCoffee.getDescription()); // 12, قهوه ساده, با شیر myCoffee = SugarDecorator(myCoffee); console.log(myCoffee.getCost(), myCoffee.getDescription()); // 13, قهوه ساده, با شیر, با شکر

مزایا و معایب:

الگوی فاساد (The Facade Pattern)

مفهوم: الگوی فاساد یک رابط ساده و سطح بالا برای یک زیرسیستم پیچیده از کلاس‌ها، کتابخانه‌ها یا APIها فراهم می‌کند. این الگو پیچیدگی زیرین را پنهان می‌کند و استفاده از زیرسیستم را آسان‌تر می‌سازد.

موارد استفاده رایج: ایجاد یک API ساده برای مجموعه‌ای از اقدامات پیچیده، مانند فرآیند تسویه حساب در یک فروشگاه اینترنتی که شامل زیرسیستم‌های موجودی، پرداخت و حمل و نقل است. مثال دیگر، یک متد واحد برای شروع یک برنامه وب است که به صورت داخلی سرور، پایگاه داده و میان‌افزارها را پیکربندی می‌کند.

پیاده‌سازی در جاوا اسکریپت:

مثال: یک فاساد برای درخواست وام مسکن

// زیرسیستم‌های پیچیده class BankService { verify(name, amount) { console.log(`در حال بررسی وجوه کافی برای ${name} به مبلغ ${amount}`); return amount < 100000; } } class CreditHistoryService { get(name) { console.log(`در حال بررسی سابقه اعتباری برای ${name}`); // شبیه‌سازی یک امتیاز اعتباری خوب return true; } } class BackgroundCheckService { run(name) { console.log(`در حال اجرای بررسی سوابق برای ${name}`); return true; } } // فاساد class MortgageFacade { constructor() { this.bank = new BankService(); this.credit = new CreditHistoryService(); this.background = new BackgroundCheckService(); } applyFor(name, amount) { console.log(`--- درخواست وام مسکن برای ${name} ---`); const isEligible = this.bank.verify(name, amount) && this.credit.get(name) && this.background.run(name); const result = isEligible ? 'تایید شد' : 'رد شد'; console.log(`--- نتیجه درخواست برای ${name}: ${result} ---\n`); return result; } } // کد کلاینت با فاساد ساده تعامل می‌کند const mortgage = new MortgageFacade(); mortgage.applyFor('John Smith', 75000); // تایید شد mortgage.applyFor('Jane Doe', 150000); // رد شد

مزایا و معایب:


الگوهای رفتاری: هماهنگی ارتباطات اشیاء

الگوهای رفتاری همگی در مورد نحوه ارتباط اشیاء با یکدیگر هستند و بر تخصیص مسئولیت‌ها و مدیریت مؤثر تعاملات تمرکز دارند.

الگوی آبزرور (The Observer Pattern)

مفهوم: الگوی آبزرور یک وابستگی یک-به-چند بین اشیاء تعریف می‌کند. زمانی که یک شیء (subject یا observable) وضعیت خود را تغییر می‌دهد، تمام اشیاء وابسته به آن (observers) به طور خودکار مطلع و به‌روز می‌شوند.

موارد استفاده رایج: این الگو اساس برنامه‌نویسی رویدادمحور است. به طور گسترده در توسعه UI (شنوندگان رویداد DOM)، کتابخانه‌های مدیریت وضعیت (مانند Redux یا Vuex) و سیستم‌های پیام‌رسانی استفاده می‌شود.

پیاده‌سازی در جاوا اسکریپت:

مثال: یک خبرگزاری و مشترکین آن

// سوژه (Subject یا Observable) class NewsAgency { constructor() { this.subscribers = []; } subscribe(subscriber) { this.subscribers.push(subscriber); console.log(`${subscriber.name} مشترک شد.`); } unsubscribe(subscriber) { this.subscribers = this.subscribers.filter(sub => sub !== subscriber); console.log(`${subscriber.name} اشتراک خود را لغو کرد.`); } notify(news) { console.log(`--- خبرگزاری: در حال پخش خبر: "${news}" ---`); this.subscribers.forEach(subscriber => subscriber.update(news)); } } // ناظر (Observer) class Subscriber { constructor(name) { this.name = name; } update(news) { console.log(`${this.name} آخرین خبر را دریافت کرد: "${news}"`); } } const agency = new NewsAgency(); const sub1 = new Subscriber('خواننده الف'); const sub2 = new Subscriber('خواننده ب'); const sub3 = new Subscriber('خواننده پ'); agency.subscribe(sub1); agency.subscribe(sub2); agency.notify('بازارهای جهانی صعودی هستند!'); agency.subscribe(sub3); agency.unsubscribe(sub2); agency.notify('پیشرفت فناوری جدیدی اعلام شد!');

مزایا و معایب:

الگوی استراتژی (The Strategy Pattern)

مفهوم: الگوی استراتژی خانواده‌ای از الگوریتم‌های قابل تعویض را تعریف می‌کند و هر کدام را در کلاس خود کپسوله می‌کند. این امر به الگوریتم اجازه می‌دهد که در زمان اجرا، مستقل از کلاینتی که از آن استفاده می‌کند، انتخاب و تعویض شود.

موارد استفاده رایج: پیاده‌سازی الگوریتم‌های مرتب‌سازی مختلف، قوانین اعتبارسنجی، یا روش‌های محاسبه هزینه حمل و نقل برای یک سایت تجارت الکترونیک (مانند نرخ ثابت، بر اساس وزن، بر اساس مقصد).

پیاده‌سازی در جاوا اسکریپت:

مثال: استراتژی محاسبه هزینه حمل و نقل

// کانتکست (Context) class Shipping { constructor() { this.company = null; } setStrategy(company) { this.company = company; console.log(`استراتژی حمل و نقل به ${company.constructor.name} تغییر یافت`); } calculate(pkg) { if (!this.company) { throw new Error('استراتژی حمل و نقل تنظیم نشده است.'); } return this.company.calculate(pkg); } } // استراتژی‌ها class FedExStrategy { calculate(pkg) { // محاسبه پیچیده بر اساس وزن و غیره const cost = pkg.weight * 2.5 + 5; console.log(`هزینه FedEx برای بسته ${pkg.weight} کیلوگرمی ${cost}$ است`); return cost; } } class UPSStrategy { calculate(pkg) { const cost = pkg.weight * 2.1 + 4; console.log(`هزینه UPS برای بسته ${pkg.weight} کیلوگرمی ${cost}$ است`); return cost; } } class PostalServiceStrategy { calculate(pkg) { const cost = pkg.weight * 1.8; console.log(`هزینه خدمات پستی برای بسته ${pkg.weight} کیلوگرمی ${cost}$ است`); return cost; } } const shipping = new Shipping(); const packageA = { from: 'New York', to: 'London', weight: 5 }; shipping.setStrategy(new FedExStrategy()); shipping.calculate(packageA); shipping.setStrategy(new UPSStrategy()); shipping.calculate(packageA); shipping.setStrategy(new PostalServiceStrategy()); shipping.calculate(packageA);

مزایا و معایب:


الگوهای مدرن و ملاحظات معماری

در حالی که الگوهای طراحی کلاسیک جاودانه هستند، اکوسیستم جاوا اسکریپت تکامل یافته و منجر به ظهور تفاسیر مدرن و الگوهای معماری در مقیاس بزرگ شده است که برای توسعه‌دهندگان امروزی حیاتی هستند.

الگوی ماژول (The Module Pattern)

الگوی ماژول یکی از رایج‌ترین الگوها در جاوا اسکریپت قبل از ES6 برای ایجاد دامنه‌های خصوصی و عمومی بود. این الگو از کلوژرها (closures) برای کپسوله کردن وضعیت و رفتار استفاده می‌کند. امروزه، این الگو تا حد زیادی با ماژول‌های نیتیو ES6 (`import`/`export`) جایگزین شده است که یک سیستم ماژول استاندارد و مبتنی بر فایل را فراهم می‌کند. درک ماژول‌های ES6 برای هر توسعه‌دهنده مدرن جاوا اسکریپت اساسی است، زیرا آنها استاندارد سازماندهی کد در برنامه‌های فرانت‌اند و بک‌اند هستند.

الگوهای معماری (MVC, MVVM)

مهم است که بین الگوهای طراحی و الگوهای معماری تمایز قائل شویم. در حالی که الگوهای طراحی مشکلات خاص و موضعی را حل می‌کنند، الگوهای معماری یک ساختار سطح بالا برای کل یک برنامه فراهم می‌کنند.

هنگام کار با فریم‌ورک‌هایی مانند React، Vue یا Angular، شما ذاتاً از این الگوهای معماری، که اغلب با الگوهای طراحی کوچک‌تر (مانند الگوی آبزرور برای مدیریت وضعیت) ترکیب شده‌اند، برای ساخت برنامه‌های قوی استفاده می‌کنید.


نتیجه‌گیری: استفاده هوشمندانه از الگوها

الگوهای طراحی جاوا اسکریپت قوانین سفت و سختی نیستند، بلکه ابزارهای قدرتمندی در زرادخانه یک توسعه‌دهنده هستند. آنها نمایانگر خرد جمعی جامعه مهندسی نرم‌افزار هستند و راه‌حل‌های ظریفی برای مشکلات رایج ارائه می‌دهند.

کلید تسلط بر آنها حفظ کردن هر الگو نیست، بلکه درک مشکلی است که هر کدام حل می‌کنند. هنگامی که در کد خود با چالشی روبرو می‌شوید - چه اتصال شدید، چه ایجاد پیچیده شیء، یا الگوریتم‌های غیر منعطف - می‌توانید به الگوی مناسب به عنوان یک راه‌حل کاملاً تعریف شده دست یابید.

توصیه نهایی ما این است: با نوشتن ساده‌ترین کدی که کار می‌کند شروع کنید. با تکامل برنامه، کد خود را در جایی که الگوها به طور طبیعی مناسب هستند، به سمت آنها بازآرایی (refactor) کنید. الگویی را در جایی که نیازی به آن نیست، به زور تحمیل نکنید. با به کارگیری عاقلانه آنها، کدی خواهید نوشت که نه تنها کاربردی، بلکه تمیز، مقیاس‌پذیر و نگهداری آن برای سال‌های آینده لذت‌بخش خواهد بود.