بررسی اجمالی design pattern های زبان جاوااسکریپت

سلام و عرض ادب یه چند هفته پیش با یکی از دوستانم صحبت می کردم که چندتا کلمه قلمبه سلمبه گفت و ازم پرسید از اینا توی پروژه هاتون استفاده می کنید یا نه؟ یکی از اون اصطلاح‌ها design pattern ها بود. من بهش گفتم استفاده میکنیم ? ولی ممکنه به شخصه با توجه به این که دروس تئوری مربوط به حوزه کاری-م رو پشت سر نگذاشتم اسم-شون رو نشنیده باشم. یه چیزی هم بگم تا عقده نشه?؛ اینکه ممکنه خیلی از اساتید این اصطلاح‌ها رو بدونند و با نمونه هاش آشنا باشند ولی یه بار هم توی پروژه ازش استفاده نکرده باشند! ? مثل اساتید مدعو ارشد دانشگاه آزاد کاشان که دنیای قشنگی داشتن برا خودشون! ?خب مزاح بسه دیگه ? بریم سراغ اصل مطلب...افرادی که این پست رو مطالعه می کنند عمدتا از این مفاهیم چه بدونن چه ندونن، دارن استفاده می کنند. توی زبان های برنامه نویسی برای بهبود روند توسعه، رفع مشکلات و حرفه ای کد زدن از یه سری مفاهیم باید استفاده کرد. design pattern ها به کد زدن ختم نمی شوند و استفاده از آن‌ها میتونه معماری پروژه-تون رو شکل بده.در ابتدا یه چیزی رو یاد آور بشیم اینکه دو تا مفهوم وجود داره، یکی pattern که مستقل از زبان هستند و دیگری idiom که وابسته به زبان هستند. این دو تا رو با هم قاطی نکنیم! pattern ها رو در ادامه توضیح خواهیم داد ولی یه مثال برای فهم idiom بزنیم. مثلا توی زبان javascript اگه یه object رو توی یه scope تعریف کنیم و توسط یه تابع اون object رو توی یه scope دیگه تغییر بدیم، این تاثیر روی object اولیه هم اعمال میشه، این جزء اصل زبان هست که برای هر زبان این اصول وجود داره و به عنوان یه idiom زبان مربوطه شناخته میشود.مثال تاثیر پذیری یک object در جاوااسکریپ:تاثیر پذیری object که جزو اصل زبان جاوااسکریپت می باشد.خب بریم سراغ الگوهای طراحی:الگوهای طراحی را عمدتا به دو دسته‌ی GRASP و GOF تقسیم می کنند. GRASP ها بیشتر اصل هستند تا الگو، یعنی به کاربرد و حوزه‌ی خاصی تعلق ندارد و در سناریوهای مختلف قابل استفاده هستند، اما الگوهای GoF، راه حل‌ مسائل تکرارشونده‌ی طراحی هستند و در بهینه کردن طراحی و توسعه و حل مشکلات ما را یاری خواهند کرد. پس به نوعی GRASP سطح بالاتر از GOF هست. در ادامه GOF رو با مثال توضیح خواهیم داد ولی برای فهم اصل GRASP یه مثال بزنیم و رد بشیم! مثلا این که مسئولیت ساختن اشیای یک کلاس را به چه کلاسی بسپاریم؟ کلاس A مسئول ساختن اشیای کلاس B است اگر حداقل یکی از شرایط زیر برقرار باشد:۱. A شامل اشیای B باشد. ۲. A اشیای B را جایی ثبت می کند. (توی db یا file و ...)۳. A از اشیای B زیاد استفاده می کند.۴. A شامل اطلاعاتی است که برای ایجاد اشیای B لازم است.این مثال به ما می فهماند که مسئولیت به کلاسی سپرده شود که اطلاعات لازم برای انجام آن کار را داشته باشد. این میشه یه اصل! برای طراحی نرم افزار باید GRASP principle ها رعایت شوند در غیر این صورت با ارورهای منطقی صادر شده از سمت زبان روبه‌رو خواهیم شد.اما الگوی (GOF (gang of four به سه دسته creational, structural و behavioral تقسیم می شوند. اگه فرصت کردید درباره‌ی اینکه GOF توسط چه افرادی طراحی شده است بخونید، جالبه!الگوهای GOF اغلب برای برنامه نویسی oop طراحی شده اند اما اگر در جاوااسکریپت به صورت برنامه نویسی functional کد می زنید (مثل reactJs) می توانید از الگوهای طراحی-ی تحت عنوان miscellaneous patterns استفاده کنید.همه‌ی pattern های GOF طراحی شده در جاوااسکریپت رو اینجا لیست می کنیم:***Creational : abstractFactorybuilderfactoryprototypesingleton ***Structural : adapterbridgecompositedecoratorfacadeflyweightproxy***Behavioral :chain of responsibilitycommandinterpreteriteratormediatormementoobserverstatestrategytemplatevisitorهمه‌ی pattern‌ های miscellaneous طراحی شده در جاوااسکریپت رو اینجا لیست می کنیم:curryingmethod chainingmoduleobject specifierrevealing moduleنکته ای که هست اینه که چند تا از این الگو‌ها رو با توجه به انعطاف پذیری جاوااسکریپت می توان با یه سری تغییرات، هم برای برنامه نویسی فانکشنال استفاده کرد هم برای برنامه نویسی شی گرا. ولی عمدتا الگوهای miscellaneous رو فانکشنال میدونند و الگوهای GOF رو نزدیک به شی گرا.یه نکته دیگه اینکه از نظر شخصی خودم، بعضی الگو هایی نظیر repository pattern که مربوط به دسترسی داده‌ها می باشد، منسوخ شده است و در سال های قبل طراحی و پیاده سازی شده است که در حال حاضر با توجه به روی کار آمدن دیگر ابزارها استفاده از آن کمتر دیده می شود. مثلا با وجود orm های قوی-ی که ظهور کرده اند repository pattern کمتر استفاده می شود.در این مقاله الگوهای miscellaneous رو کامل توضیح میدم و به صورت random به توصیف چندتا از الگوهای GOF نیز می پردازم. شرح الگوهای miscellaneous :در ادامه مثال هایی که برای هر الگو زده شده است کاملا واضح است و با توجه به این که میخواهم پست مختصر و مفید باشه از توضیحات اضافی پرهیز کردم. سعی کردم به صورت random برای هر الگو، نمونه کدهایی که از ورژن های 5 و 6 استاندارد ECMAScript، و سوپرست های typeScript و coffeeScript رو براتون بزنم که اگه با سینتکس هرکدوم از این فضاها آشنا هستید بتونید راحت‌تر الگوی زده شده رو درک کنید.الگوی currying : این الگو جزو محبوب ترین الگوهای functional programming می باشد. مثال:// ES5 var getProfile = function (name) { return function (age) { return function (gender) { var str = "name: " + name + ", age: " + age + ", gender: " + gender; return str; }; }; }; var myProfile = getProfile("akbar")(22)("male"); console.log(myProfile); //output: name: akbar, age: 22, gender: maleالگوی method chaining : این الگو زنجیره سازی کد رو پیاده سازی می کند، حتما به کتابخانه jquery تسلط دارید، برای درک بهتر این موضوع به مثال زیر دقت کنید:$('#box').css('background', 'red'); $('#box').height(100); $('#box').fadeIn(200);کد بالا را میتوان به صورت زیر هم نوشت:$('#box').css('background', 'red').height(100).fadeIn(200);این کار را بارها انجام داده اید ولی احتمالا نمی دانستید این یک method chaining pattern است! مثال:// typeScript interface Person { setName(name: string): Person; sayHello(): string | never; } const person: Person = { setName(name: string): Person { this.name = name; return this; }, sayHello(): string | never { if (!this.name) { throw { type: "No data", message: "a person needs a name before to say hello." }; } return `hello, ${this.name}!`;} }; try { console.log(person.setName("akbar").sayHello()); // !این یک زنجیره می باشد } catch (e) { console.log(e.type + " - " + e.message); }مثالی دیگر:// coffeeScript person = setName: (@name) -> @ sayHello: -> if !@name throw type: "No data" message: "a person needs a name before to say hello." "hello, #{this.name}!" try console.log person.setName("akbar").sayHello() #!این یک زنجیره می باشد catch e console.log #{e.type} - #{e.message}الگوی module : موارد استفاده از module pattern برای کپسوله سازی کاربرد دارد. همانطور که میدانید جاوااسکریپت به طور پیشفرض از کلمات کلیدی دسترسی public، private یا protected استفاده نمی کند. اما با استفاده از closures می توان دسترسی شی‌ها را تعیین کرد. closures به حفظ هر حالت و محافظت از حریم خصوصی در ماژول مربوطه کمک میکند. ما میتوانیم نرم افزار خود را ماژول بیس پیاده کنیم تا از مدیریت دسترسی ها و کپسوله سازی بهره ببریم. مثال:// ES6 const Person = (() => { let secretName; return class Person { constructor(name, _secretName) { this.name = name; // public attributes secretName = _secretName; // private attribute } sayHello() { // public methods return `hello, ${this.name}!` } getSecretName() { return secretName; } }})(); let me = new Person("akbar", "gholi"); console.log(me.name); // ok console.log(me.sayHello()); // ok console.log(me.getSecretName()); // ok console.log(me.secretName); // error or undefined! اینجا به دلیل عدم دسترسی با مشکل مواجه میشیم. به نظرتون در خط بالا با ارور مواجه میشیم یا بدون ارور آندی فایند چاپ می شود؟خروجی کنسول گویای همه چیز هست!الگوی object specifier pattern : همه چیز از مثال زیر مشهود است دی:// ES6 const profile = ({ name, age, gender }) => `name: ${name} age: ${age} gender: ${gender}`; let myProfile = profile({ age: 22, gender: "male", name: "akbar"}); console.log(myProfile);الگوی revealing module pattern : این الگو شباهت زیادی با الگوی module دارد. چون هر دو مبحث کلمات کلیدی دسترسی (public, private, protected) رو برای ما ملموس میکنند. تفاوت آن‌ها در این است که توی module داخل function، کلاس return شده است و ما برای استفاده از آن مجبوریم یک شی از آن را new کنیم. ولی توی revealing module داخل function یک آبجکت return میشود و نیازی نیست برای دسترسی به آن شی از new استفاده کرد. به دلیل همین تفاوت، module pattern را جزو الگوی های oop هم در نظر می گیرند ولی revealing module pattern کاملا فانکشنال می باشد. مثال:// ES6 const Person = (() => { const name = "akbar"; // public variables const secretName = "gholi"; // private functions const sayHello = () => `hello, ${name}!`; const getSecretName = () => secretName; // revealed module return { name, sayHello, getSecretName }; })(); console.log(Person.name); // ok console.log(Person.sayHello()); // ok console.log(Person.secretName); //error or undefined! console.log(Person.getSecretName()); // okخروجی کنسول گویای همه چیز هست!خب این از این!شرح الگوهای GOF :الگوی constructor pattern :همانطور که می دانید، جاوا اسکریپت از مفهوم کلاس‌ها پشتیبانی نمی کند، اما از توابع سازنده خاصی که با اشیاء کار می کنند پشتیبانی می کند. به سادگی با صدا زدن یک تابع سازنده با کلمه کلیدی جدید، ما می توانیم به جاوا اسکریپت بگوییم که تابع مانند یک سازنده رفتار کند و یک شی جدید را با اعضای تعریف شده توسط آن تابع ایجاد کند. مثال:function getProfile(name, mobile) { this.name = name; this.mobile = mobile; this.getText = function () { return this.name + "'s mobile number is " + this.mobile; }; } var profile = new getProfile("akbar", 09351234567); // ایجاد کردن یه instance جدید از تابع مربوطه console.log(profile.getText()); // output: akbar's mobile number is 09351234567الگوی observer pattern :اگر تا کنون یک رویداد listener با addEventListener نوشته اید یا از متدهایی jquery مثل live، delegate یا on استفاده کرده اید شما دانسته یا ندانسته از الگوی observer استفاده کرده اید.وقتی در یک نرم افزار یک قسمت از برنامه تحت یک رویداد، تغییر می کند اگر دیگر قسمت های نرم افزار بخواهند از این تغییر تاثیر بگیرند و به آن وابسته باشند، استفاده از الگوی observer پیشنهاد میشود تا سایر قسمت ها هم وابسته به آن شی به روز شوند.توی برنامه نویسی وب، سمت کلاینت اگر از فریم ورک استفاده شود به دلیل اینکه همه‌ی فریم ورک های کلاینتی single page تعبیه شده اند و توی صفحات مختلف صفحه رفرش نمی شود مفهوم observer برای به روز کردن اطلاعات به شدت ملموس است تا جایی که کتابخانه‌هایی مثل rxJs و redux یا vuex بر پایه‌ی همین مفهوم مهم برای فریم ورک‌ها release شدند. ابتدای استفاده از این کتابخونه‌ها یه مقدار دشوار می باشد ولی با درک مفاهیم-شون میشه نتیجه مطلوبی گرفت. جلوگیری از کدهای اضافی برای شی‌ های ناظر (شنونده رویداد) یکی از مزایای استفاده از این الگوست، به طوری که تعداد شنونده‌های رویداد مهم نیستند و مهم نقطه شروع عملیات است که از رویداد اولیه نشئت می گیرد. این الگو جوانب زیادی دارد مثلا اینکه کدوم شنونده نتونه رویداد رو بشنوه یا اینکه شنود رویداد تا چه زمانی ادامه داشته باشه و جوانب دیگر که با توجه به کاری که میخواهید انجام بدید می تونید ازش-ون استفاده کنید. مثلا یه جدول برای نمایش اطلاعات موجود می باشد، کاربر چندتا رکورد رو حذف می کند، توی کمپوننت همسایه این صفحه (مثلا یه جایی مثل سایدبار، نوبار، پشت محتوای صفحه، یا کمپوننت های کشویی که با توجه به ui/ux مورد استفاده قرار گرفته شده است) اطلاعات بدون درخواست مجدد به سرور به روز می شوند.توی یک مثال ساده با استفاده از pure javascript تحت سینتکس ES6 میخواهیم هر موقع محتویات یه input تغییر کرد تعداد کاراکتر‌های محتوای تایپ شده نمایش داده شود:class InputEventObserver { constructor() { this.observers = []; } subscribe(value) { this.observers.push(value); } broadcast(data) { this.observers.forEach((subscriber) => subscriber(data)); } } const textObserver = new InputEventObserver(); textObserver.subscribe((text) => { const textLength = document.getElementById('textLength'); textLength.textContent = text; }); const input = document.getElementById('input'); input.addEventListener('keyup', () => textObserver.broadcast(v.value));یه چیزی که ذهنم رو درگیر کرده اینه که توی برنامه نویسی وب، سمت سرور آیا نیاز به این الگو می شود یا نه؟!توی کد نویسی سمت سرور lifecycle هر request با رسیدن به ته اکشن توی کنترلر مربوطه تمام میشه، آیا نیازی هست که دیگر اجزای سیستم، از شروع این رویداد با خبر شوند یا چی؟! ? اگه فردی میتونه با یه مثال ملموس منو شیرفهم کنه توی تلگرام بهم پیام بده. ممنونم. ?my telegram id => https://t.me/mojtaba_ramezaniالگوی prototype pattern : همانطور که می دانید الگوی prototype نوع اشیاء را با استفاده از نمونه اولیه مشخص می کند و اشیاء جدید را با کپی کردن این نمونه اولیه، ایجاد می کند. هر تابع، ماژول یا کلاس ظرفیت این را دارد که یه سری instance هایی برایش تعریف شود. مثلا برای تابع console یه سری instance هایی مثل log، clear، table، info و ... توی جاوااسکریپت تعریف شده است که prototype های console نامیده می شوند. بسته به نیازتون میتوانید برای تابع، کلاس یا ماژول های پروژه تون prototype تعریف و ازش استفاده کنید.// ES5 var PaperSheet = function () { if (this.constructor === PaperSheet) { throw new Error("You cannot instantiate an abstract class!"); } }; PaperSheet.prototype.photocopy = function () { throw new Error("You cannot call an abstract method!"); }; var Invoice = function (price, currency) { this.price = price; this.currency = currency; }; Invoice.prototype = Object.create(PaperSheet.prototype); Invoice.prototype.constructor = Invoice; Invoice.prototype.photocopy = function () { var clone = Object.create(Object.getPrototypeOf(this)); for (var prop in this) { if (this.hasOwnProperty(prop)) { clone[prop] = this[prop]; } } return clone; }; module.exports = Invoice;توی برنامه نویسی وب، سمت کلاینت، استفاده از الگوی prototype بسیار رایج هست. برای مولفه‌ها و ویژگی های مختص ورژن های جدید اکمااسکریپت که مرورگرها پشتیبانی نمی کنند می توان با فهم ویژگی مربوطه، خودمون براش یه prototype ست کنیم تا برای همه‌ی مرورگرها قابل اجرا باشد.دیگه حوصله نبود بقیه الگوهای GOF رو توضیح بدم. برید و راضی باشید دیگه! ?توی مقاله‌ی بعدی اگه عمری بود میخواهم درباره‌ی (SOA (service oriented architecture یا همون معماری سرویس گرا، هر چیزی که ازش بخونم و یاد بگیرم رو براتون بنویسم. ?