82- آموزش جاوااسکریپت | آموزش Module Pattern و Revealing Module Pattern
در این بخش با Module Pattern و Revealing Module Pattern در JavaScript آشنا میشویم؛ دو الگوی مهم برای ماژولار کردن کد، مدیریت اعضای خصوصی و عمومی، و ساختاردهی بهتر پروژههای جاوااسکریپتی.
در این بخش، اولین الگویی که بررسی میکنیم Module Pattern است و سپس به نسخهای نزدیک به آن یعنی Revealing Module Pattern میپردازیم.
با معرفی ماژولهای استاندارد در JavaScript، امکان استفاده از فایلهای جداگانه و import/export فراهم شده است. با این حال، Module Pattern همچنان یکی از الگوهای مهم برای درک مفهوم کپسولهسازی، مدیریت دسترسی و سازماندهی کد به شمار میرود.
این الگو به ما کمک میکند بخشهایی از کد را بهصورت ماژولهای مستقل بنویسیم؛ ماژولهایی که میتوانند شامل اعضای خصوصی و عمومی باشند.
Module Pattern چیست؟
در Module Pattern، کد معمولاً داخل یک تابع خوداجرا یا IIFE قرار میگیرد.
IIFE مخفف عبارت Immediately Invoked Function Expression است؛ یعنی تابعی که بلافاصله پس از تعریف اجرا میشود.
ساختار کلی این الگو به این صورت است:
const UICtrl = (function () {
// private members
const text = 'Hello World';
const changeText = function () {
const element = document.querySelector('h1');
element.textContent = text;
};
return {
// public members
callChangeText: function () {
changeText();
console.log(text);
}
};
})();
IIFE چگونه کار میکند؟
برای ساخت IIFE، تابع را داخل پرانتز قرار میدهیم تا به یک expression تبدیل شود و سپس با () آن را بلافاصله اجرا میکنیم.
(function () {
console.log('I run immediately');
})();
این ساختار باعث میشود متغیرها و توابع داخل آن، از بیرون بهصورت مستقیم در دسترس نباشند.
اعضای خصوصی و عمومی در Module Pattern
ایدهی اصلی Module Pattern این است که:
- متغیرها و توابعی که داخل ماژول تعریف میشوند، خصوصی هستند.
- هر چیزی که داخل
returnبرگردانده شود، عمومی خواهد بود.
در مثال زیر، text و changeText خصوصی هستند و فقط callChangeText از بیرون در دسترس قرار میگیرد:
const UICtrl = (function () {
const text = 'Hello World';
const changeText = function () {
const element = document.querySelector('h1');
element.textContent = text;
};
return {
callChangeText: function () {
changeText();
console.log(text);
}
};
})();
سپس میتوانیم از بیرون ماژول، فقط متدی را که عمومی کردهایم فراخوانی کنیم:
UICtrl.callChangeText();
چرا changeText و text از بیرون در دسترس نیستند؟
چون این دو عضو داخل ماژول تعریف شدهاند و در return قرار نگرفتهاند. بنابراین دسترسی مستقیم به آنها ممکن نیست.
نمونههای زیر خطا ایجاد میکنند یا undefined برمیگردانند:
UICtrl.changeText(); // Error
console.log(UICtrl.text); // undefined
این ویژگی یکی از مهمترین مزایای Module Pattern است؛ چون باعث میشود فقط بخشهای ضروری را در اختیار سایر قسمتهای برنامه قرار دهیم.
کاربرد Module Pattern در پروژههای واقعی
در پروژههای بزرگتر میتوان از این الگو برای تفکیک مسئولیتها استفاده کرد. برای مثال، در یک برنامهی ثبت کالری میتوان چند ماژول مجزا داشت:
- Item Controller برای مدیریت دادههای مربوط به آیتمها
- UI Controller برای مدیریت رابط کاربری
- Storage Controller برای کار با
localStorage - App Controller برای هماهنگی بخشهای مختلف و راهاندازی برنامه
این ساختار باعث میشود کد خواناتر، منظمتر و قابل نگهداریتر باشد.
Revealing Module Pattern چیست؟
الگوی Revealing Module Pattern بسیار شبیه Module Pattern است، اما نحوهی عمومی کردن متدها در آن کمی متفاوت است.
در این الگو، بهجای تعریف مستقیم متدهای عمومی داخل return، متدهای خصوصی را از طریق یک شیء برمیگردانیم. به بیان ساده، مشخص میکنیم کدام متدهای خصوصی باید «آشکار» یا revealed شوند.
نمونهی Revealing Module Pattern
در مثال زیر، یک ماژول برای مدیریت دادهها تعریف میکنیم:
const ItemCtrl = (function () {
const data = [];
function add(item) {
data.push(item);
console.log('Item Added...');
}
function get(id) {
return data.find(function (item) {
return item.id === id;
});
}
return {
add: add,
get: get
};
})();
اکنون میتوانیم از متدهای عمومی استفاده کنیم:
ItemCtrl.add({ id: 1, name: 'John' });
ItemCtrl.add({ id: 2, name: 'Mark' });
console.log(ItemCtrl.get(1));
console.log(ItemCtrl.get(2));
نسخهی کوتاهتر با سینتکس جدید
همین ساختار را میتوان کوتاهتر هم نوشت:
const ItemCtrl = (function () {
const data = [];
function add(item) {
data.push(item);
console.log('Item Added...');
}
function get(id) {
return data.find(item => item.id === id);
}
return {
add,
get
};
})();
تفاوت Module Pattern و Revealing Module Pattern
تفاوت اصلی این دو الگو در نحوهی تعریف اعضای عمومی است:
Module Pattern
در این الگو معمولاً متدهای عمومی را مستقیماً داخل return تعریف میکنیم:
return {
callChangeText: function () {
changeText();
console.log(text);
}
};
Revealing Module Pattern
در این الگو، متدهای خصوصی را تعریف میکنیم و سپس همانها را در return آشکار میسازیم:
return {
add: add,
get: get
};
اگر متدی را در return قرار ندهیم چه میشود؟
اگر یک تابع یا متغیر در return قرار نگیرد، از بیرون ماژول قابل دسترسی نخواهد بود.
برای مثال:
const ItemCtrl = (function () {
const data = [];
function add(item) {
data.push(item);
}
function get(id) {
return data.find(item => item.id === id);
}
return {
add: add
};
})();
در این حالت:
ItemCtrl.add({ id: 1, name: 'John' }); // works
ItemCtrl.get(1); // Error: get is not a function
نکتهای درباره نامگذاری اعضای خصوصی
گاهی در بعضی کدها برای نمایش خصوصی بودن متغیرها از _ در ابتدای نام استفاده میشود؛ مثل:
const _data = [];
این فقط یک قرارداد نامگذاری است و JavaScript بهصورت ذاتی صرفاً با این علامت چیزی را خصوصی نمیکند. خصوصی بودن در اینجا بهدلیل قرار گرفتن متغیر در scope داخلی ماژول است.
کدام الگو بهتر است؟
Revealing Module Pattern معمولاً ظاهر تمیزتر و خواناتری دارد، اما Module Pattern در بعضی سناریوها انعطاف بیشتری در اختیار شما میگذارد.
انتخاب بین این دو الگو به نیاز پروژه و سبک کدنویسی شما بستگی دارد. مهمتر از انتخاب الگو، درک درست مفهوم private و public در ساختار ماژولها است.
جمعبندی
در این بخش با دو الگوی مهم در JavaScript آشنا شدیم:
- Module Pattern
- Revealing Module Pattern
این دو الگو برای ساختاردهی بهتر کد، جلوگیری از دسترسی مستقیم به جزئیات داخلی، و طراحی ماژولهای قابل نگهداری بسیار مفید هستند.
در ادامهی آموزش، میتوان الگوهای دیگری مانند Singleton Pattern را نیز بررسی کرد؛ الگویی که از نظر ساختار به Module Pattern نزدیک است، اما برای ساخت تنها یک نمونه از یک شیء به کار میرود.