در این آموزش قصد داریم تا با دیزاین پترنی تحت عنوان 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 در سکان آکادمی مراجعه نمایید.)