در این آموزش قصد داریم تا با دیزاین پترنی تحت عنوان Singleton در قالب مثالی کاربردی در زبان برنامهنویسی جاوااسکریپت آشنا شویم اما پیش از شروع بحث و به منظور آشنایی با مفهوم دیزاین پترن و کاربردهای آن در توسعۀ اصولی اپلیکیشن توصیه میکنیم به مقالۀ Design Pattern چیست؟ مراجعه نمایید.
دیزاین پترن سینگلتون زیرشاخۀ الگوهای Creational قرار میگیرد و به منظور فراهم کردن قابلیت ساخت تنها یک آبجکت از روی یک کلاس بهخصوص مورد استفاده قرار میگیرد به طوری که نمونهسازی از کلاس مد نظر تنها یک مرتبه امکانپذیر بوده و تمامی آبجکتهای جدید ساختهشده از آن به آبجکت مذکور ارجاع میدهند بدین معنی که ساخت آبجکت جدید از کلاس مد نظر و فراخوانی کانستراکتور آن منجر به ریترن شدن تنها یک آبجکت یکسان میشود.
همچنین لازم به یادآوری است که زبان برنامهنویسی جاوااسکریپت به طور پیشفرض چنین قابلیتی دارا است به طوری که آبجکتها در این زبان به صورت اصطلاحاً Pass By Reference تعریف میشوند بدین معنی که هر یک از آبجکتهای ساختهشده در این زبان به فضایی منحصربهفرد از حافظه ارجاع میدهند (جهت کسب اطلاعات بیشتر در این مورد توصیه میکنیم به مقالۀ آشنایی با تفاوت Pass by Value و Pass By Reference مراجعه کنید.) در واقع، هنگام استفاده از آبجکت مذکور در برنامه به فضایی ارجاع میدهیم که آبجکت مد نظر اشاره میکند و بنابراین انتساب آبجکتی به یک متغیر به معنای اختصاص محتویات آبجکت به متغیر مذکور نمیباشد بلکه صرفاً به محتویات آبجکت مد نظر ارجاع میدهد و بنابراین ساخت یک کپی از آبجکت مد نظر و انتساب آن به متغیری دیگر منجر بدین میشود تا هر دو متغیر مذکور به یک آدرس مشابه اشاره کنند به طوری که اِعمال هر تغییری در آبجکت مذکور روی هر دو متغیر نیز اِعمال میشود. برای درک بهتر این موضوع، مثال زیر را مد نظر قرار میدهیم:
const user = {
name: 'Peter',
age: 25,
job: 'Teacher',
greet: function() {
console.log('Hello');
}
};
در کد فوق، آبجکتی تحت عنوان user ساختهایم و یکسری پراپرتی به همراه مقادیر متناظر را به آن اختصاص دادهایم که برای مثال پراپرتی greet دارای مقداری معادل یک فانکشن بینام است و در آن گفتهایم در صورت استفاده از پراپرتی greet اجرا شده و استرینگ «Hello» را در خروجی چاپ کند. به عنوان مثال داریم:
user.greet(); // returns Hello
همانطور که اشاره کردیم، هر آبجکت در این زبان فضایی منحصربهفرد از حافظه را اِشغال میکند و در حین استفاده از این آبجکت صرفاً به آدرس مربوط به آن ارجاع میدهیم. حال اگر بخواهیم متغیری جدید تعریف کرده و مقادیر آبجکت user را در آن کپی کنیم و در ادامه برخی پراپرتیهای آن را تغییر دهیم، کدی مانند زیر خواهیم داشت:
const user1 = user;
user1.name = 'Mark';
در حقیقت، کد فوق بدین معنی است که اکنون متغیر user1 و user هر دو به یک آدرس در حافظه اشاره میکنند و بدین ترتیب با تعریف مقدار استرینگ «Mark» برای پراپرتی name مقدار این پراپرتی در آبجکت user نیز به استرینگ مذکور تغییر پیدا میکند که جهت تست کد زیر مد نظر قرار میدهیم:
console.log(user.name);
console.log(user1.name);
console.log(user === user1);
در ابتدا گفتهایم مقدار منتسب به پراپرتی name از آبجکت user و در سطر دوم نیز مقدار منتسب به پراپرتی name از آبجکت user1 در خروجی چاپ شود و در سطر آخر نیز آبجکتهای user1 و user را از طریق اپراتور مقایسۀ === از نظر نوع و مقدار با هم مقایسه کردهایم به طوری که در خروجی خواهیم داشت:
Mark
Mark
true
همانطور که ملاحظه میشود، مقدار پراپرتی name برای هر دو آبجکت user1 و user به استرینگ «Mark» تغییر یافته است بدین معنی که دو آبجکت به آدرسی یکسان اشاره میکنند و دستور سطر آخر نیز منجر به چاپ مقدار بولین true در خروجی میشود که حاکی از آن است هر دو آبجکت دقیقاً یکسان هستند.
در ادامه الگوی طراحی سینگلتون را با بهکارگیری مفهوم کانستراکتور در این زبان برنامهنویسی پیادهسازی میکنیم که برای این منظور کدی مانند زیر خواهیم داشت:
let instance = null;
function User() {
if (instance) {
return instance;
}
instance = this;
this.name = 'Peter';
this.age = 25;
return instance;
}
const user1 = new User();
const user2 = new User();
console.log(user1 === user2);
در کد فوق، متغیری به نام instance تعریف کرده و مقدار اولیۀ آن را برابر با null قرار دادهایم و در ادامه کلاسی به نام User تعریف کردهایم که در آن گفتهایم در صورت ساخت آبجکتی از روی این کلاس، دستور if چک کند که آیا متغیر instance پُر است یا خیر که در صورت مثبت بودن شرط، آبجکت مذکور در خروجی ریترن میشود و در غیر این صورت با بهکارگیری کیورد this گفتهایم کلاس فعلی یا همان User به متغیر instance اختصاص یابد سپس پراپرتیهای name و age را به ترتیب با مقادیر «Peter» و عدد 25 ایجاد کرده و در ادامه آبجکت instance ریترن شده است (این کلاس اصطلاحاً Prototype-based نام دارد و همانطور که ملاحظه میشود، با کیورد function ساخته میشود.) خروجی بلوک کد فوق به صورت زیر خواهد بود:
true
به منظور تست این دیزاین پترن، دو آبجکت از روی کلاس User تحت عناوین user1 و user2 ساخته و در سطر بعد چک کردهایم که آیا این دو آبجکت از نظر مقدار و نوع با هم برابر هستند یا خیر که میبینیم مقدار بولین true در خروجی ریترن میشود؛ به عبارت دیگر، دو آبجکت با نامهای متفاوت از کلاس ساختهایم اما این در حالی است که هر دو به یک آدرس یکسان از آبجکتی منحصربهفرد ارجاع میدهند.
در نسخهٔ ES6 شاهد افزوده شدن کیورد class هستیم که برای دولوپرهای زبانهایی همچون پیاچپی، جاوا، سیشارپ و ... نامآشنا است. جهت آشنایی با سازوکار پیادهسازی الگوی طراحی سینگلتون در زبان جاوااسکریپت، مثال زیر را مد نظر قرار میدهیم:
class Database {
constructor(data) {
if (Database.exists) {
return Database.instance;
}
this.dataProperty = data;
Database.instance = this;
Database.exists = true;
return this;
}
getData() {
return this.dataProperty;
}
}
const mongo = new Database('mongo');
console.log(mongo.getData());
در بلوک فوق، کلاسی ساختهایم تحت عنوان Database که فرضاً مسئول برقراری ارتباط با دیتابیس است. اما پیش از هر چیز، کدهای داخل کانستراکتور این کلاس را تشریح میکنیم.
کانستراکتور این کلاس یک پارامتر ورودی میگیرد تحت عنوان data و داخل این فانکشن از طریق یک دستور شرطی چک کردهایم که آیا مقدار پراپرتی exists برابر با true است یا خیر که اگر true بود، پراپرتی instance ریترن میشود و در غیر این صورت، از طریق دستور this.dataProperty مقدار پراپرتی dataProperty را برابر با data قرار میدهیم، مقادیر this و true را به ترتیب برای پراپرتیهای instance و exists در نظر گرفته و در نهایت this را ریترن میکنیم (کیورد this در اینجا منظور کلاس Database است.) در ادامه یک فانکشن به اصطلاح Getter تحت عنوان ()getData نوشتهایم که این وظیفه را دارا است تا dataProperty را ریترن کند.
حال به منظور تست کلاس فوق آبحکتی تحت عنوان mongo از روی کلاس Database ساخته و استرینگ «mongo» را به عنوان پارامتر ورودی کانستراکتور این کلاس در نظر گرفتهایم سپس متد ()getData را فراخوانی کردهایم به طوری که خروجی داخل کنسول به نمایش میآید:
mongo
مجدد آبجکتی جدید تحت عنوان mysql به صورت زیر میسازیم:
const mongo = new Database('mongo');
console.log(mongo.getData());
const mysql = new Database('mysql');
console.log(mysql.getData());
و به عنوان خروجی خواهیم داشت:
mongo
mongo
میبینیم که دیزاین پترن سینتگلتون که این وظیفه را دارا است تا صرفاً یک آبجکت از روی کلاس فوق بسازد وظیفهاش را به درستی انجام داده به طوری که حتی با ساخت آبجکت جدیدِ mysql کماکان پارامتر ورودی آبجکت اول ریترن میشود.
جمعبندی
در این آموزش به بررسی نحوۀ پیادهسازی دیزاین پترن سینگلتون در زبان برنامهنویسی جاوااسکریپت پرداخته و دیدیم که با بهکارگیری آن امکان ساخت تنها یک آبجکت از کلاس مد نظر را داریم به طوری که از تمامی نقاط برنامه و به صورت گلوبال در دسترس است به علاوه اینکه دیدیم جاوااسکریپت به صورت پیشفرض قابلیت ارجاع به آبجکتی مشابه را برای دولوپرها فراهم کرده است (به منظور آموزش گامبهگام نحوۀ پیادهسازی دیزاین پترن سینگلتون در زبان برنامهنویسی پیاچپی و آشنایی با کاربرد آن در برنامهنویسی شیئگرا میتوانید به آموزش آشنایی با الگوی طراحی Singleton در سکان آکادمی مراجعه نمایید.)
