آموزش جاوا اسکریپت – جلسه ۱۶ – دیزاین پترن

در جلسه پانزدهم از آموزش جاوا اسکریپت، درباره نحوه تعریف استایل های CSS در زبان جاوا اسکریپت صحبت کردیم. در این جلسه می خواهیم دیزاین پترن را معرفی کنیم و فواید آن را بیان کنیم و انواع دیزاین پترن در Javascript را معرفی کنیم.
وقتی شما می خواهید یک پروژه جدید را استارت بزنید، بلافاصله شروع به کدنویسی نمی کنید. بلکه ابتدا لازم است اهداف پروژه را درک کنید، سپس ویژگی ها و مشخصه های پروژه را استخراج کنید، در مرحله بعد اگر پروژه شما کوچک است می توانید کدنویسی آن را آغاز کنید و اگر پروژه پیچیده و سنگینی دارید، بهتر است دیزاین پترن مناسب را انتخاب کنید.
دیزاین پترن (Design-Pattern) چیست؟
در علم مهندسی نرم افزار، دیزاین پترن یک راه حل قابل استفاده مجدد است که می تواند برای حل مشکلات موجود در طراحی نرم افزار مورد استفاده قرار بگیرد. این راه حل ها (solutions) توسط توسعه دهندگان حرفه ای نرم افزار ایجاد و استفاده می شوند.
چرا از دیزاین پترن استفاده کنیم؟
برخی از فواید استفاده از دیزاین پترن:
بسیاری از برنامه نویسان عقیده دارند انتخاب یک دیزاین پترن برای توسعه نرم افزار فقط اتلاف وقت است و برخی از آنها نیز با نحوه کار با آنها آشنا نیستند. اما استفاده از دیزاین پترن مناسب می تواند به کدنویسی خواناتر منجر شود و همچنین کد نهایی قابلیت توسعه و نگهداری راحت تری خواهد داشت.
علاوه بر موارد بالا، داشتن یک دیزاین پترن مناسب می تواند منجر به داشتن یک فرهنگ لغت مشترک بین برنامه نویس ها شود. بعنوان مثال اگر شما از یک decorator pattern استفاده می کنید، اگر یک برنامه نویس دیگر بخواهد کد شما را بخواند تا مشکل احتمالی کد ر متوجه شود، بجای اینکه روی متوجه شدن کد شما وقت بگذارد روی بیزینس کد شما و نحوه رفع مشکل وقت خواهد گذاشت.
انواع دیزاین پترن در جاوا اسکریپت:
حالا که بطور کلی با دیزاین پترن و کاربرد آنها آشنا شدیم، نوبت آنست که به سراغ معرفی دیزاین پترن های زبان جاوا اسکریپت برویم:
پترن ماژول (Module Pattern):
ماژول تکه ای کد است که مستقل از سایر بخش های کد می باشد و می توانیم آن را بطور مجزا آپدیت کنیم. بدون اینکه روی بقیه کد تاثیری بگذارد. همچنین با استفاده از ماژول پترن می توان اسکوپ های جداگانه ای برای متغیرهای برنامه تعریف کرد که این باعث خلوت شدن برنامه و جلوگیری از شلوغی کدها می شود. علاوه بر این موارد، می توان از یک ماژول در پروژه های مختلف استفاده کرد.
ماژول ها بخشی از اکثر برنامه های مدرن جاوا اسکریپتی هستند که باعث تمیزی کد و مستقل بودن بخش های مختلف آن می شود. روش های مختلفی برای ایجاد ماژول در javascript وجود دارد. یکی از آنها ماژول پترن می باشد.
در جاوا اسکریپت، برخلاف سایر زبان های برنامه نویسی نمی توان سطوح دسترسی به متغیرها (Access Modifier) را تعریف کرد. بدین معنی که در حالت پیش فرض نمی توان در جاوااسکریپت یک متغیر private یا public تعریف کرد. کاربرد دیگر ماژول پترن شبیه سازی کپسوله سازی یا encapsulation می باشد.
بعنوان مثال:
const myModule = (function() { const privateVariable = 'Hello World'; function privateMethod() { console.log(privateVariable); } return { publicMethod: function() { privateMethod(); } }})(); myModule.publicMethod();
از آنجائیکه این پترن بصورت IIFE (immediately-invoked function expression) می باشد، بلافاصله اجرا می شود و آبجکت خروجی به متغیر myModule نسبت داده می شود.
دقت کنید که در کد بالا، آبجکت بازگشتی می تواند به متغیرهای تعریف شده در داخل IIFE دسترسی داشته باشد. بنابراین متغیرها و توابعی که داخل IIFE تعریف می شوند از خارج از بلاک myModule قابل دسترس نیستند و در واقع private می باشند.
پس از اینکه کد اجرا شد، متغیر myModule شبیه زیر خواهد بود:
const myModule = { publicMethod: function() { privateMethod(); }};
بنابراین می توانیم متد publicMethod() را اجرا کنیم:
// Prints 'Hello World' module.publicMethod();
ماژول های اکما اسکریپت 6 (ES6):
تا قبل از ES6 جاوا اسکریپت ماژول پیش فرض (built-in) نداشت. بنابراین توسعه دهندگان مجبور بودند برای پیاده سازی ماژول، به کتابخانه های third-party یا دیزان پترن ها رجوع کنند. اما جاوا اسکریپت در نسخه ES6 خود بطور پیش فرض از ماژول ها پشتیبانی می کند و قابلیت پیاده سازی ماژول را بدون نیاز به ابزار دیگری به برنامه نویس می دهد.
ماژول های ES6 در فایل ذخیره می شوند که در هر فایل فقط می تواند یک ماژول تعریف شود. هر چیزی در ماژول ها بطور پیش فرض private می باشند. توابع، متغیرها و کلاس ها با کلمه کلیدی export می توانند برون بری شوند و مورداستفاده سایر ماژول ها و کدهای پروژه قرار بگیرند. دقت کنید که کدهای داخل ماژول همیشه در حالت strict mode اجرا می شوند.
اکسپورت کردن یک ماژول:
دو راه برای اکسپورت کردن توابع و متغیرها در جاوا اسکریپت وجود دارد:
- با تعریف کلمه کلیدی export در قبل از نام تابع یا متغیر. بعنوان مثال:
// utils.jsexport const greeting = 'Hello World';export function sum(num1, num2) { console.log('Sum:', num1, num2); return num1 + num2; }export function subtract(num1, num2) { console.log('Subtract:', num1, num2); return num1 - num2; }// This is a private functionfunction privateLog() { console.log('Private Function'); }
- با افزودن کلمه کلیدی export به انتهای قطعه کد (که شامل نام تابع یا متغیری است که می خواهیم اکسپورت کنیم):
// utils.jsfunction multiply(num1, num2) { console.log('Multiply:', num1, num2); return num1 * num2; }function divide(num1, num2) { console.log('Divide:', num1, num2); return num1 / num2; }// This is a private functionfunction privateLog() { console.log('Private Function'); }export {multiply, divide};
ایمپورت کردن ماژول:
همانند اکسپورت کردن یک ماژول، برای ایمپورت کردن ماژول نیز دو راه داریم:
- ایمپورت کردن چند ماژول در یک دستور:
// main.js// importing multiple items import { sum, multiply } from './utils.js';console.log(sum(3, 7)); console.log(multiply(3, 7));
- ایمپورت کردن تمام ماژول ها:
// main.js// importing all of moduleimport * as utils from './utils.js';console.log(utils.sum(3, 7)); console.log(utils.multiply(3, 7));
تعریف نام مستعار برای export و import:
می توان در هنگام ایمپورت و اکسپورت کردن توابع یا متغیرها، برای جلوگیری از تداخل نام ها از نام مستعار یا Alias استفاده کرد.
مثالی از تعریف نام مستعار بهنگام export کردن:
// utils.jsfunction sum(num1, num2) { console.log('Sum:', num1, num2); return num1 + num2; }function multiply(num1, num2) { console.log('Multiply:', num1, num2); return num1 * num2; }export {sum as add, multiply};
مثالی از تعریف alias در ایمپورت:
// main.jsimport { add, multiply as mult } from './utils.js';console.log(add(3, 7)); console.log(mult(3, 7));
پترن سینگلتون (Singleton):
سینگلتون یک آبجکت است که فقط می تواند یکبار تعریف شود. یک پترن سینگلتون یک نمونه (instance) از کلاس را ایجاد می کند (اگر از قبل وجود نداشته باشد). در صورتیکه نمونه از قبل تعریف شده باشد یک رفرنس به آن آبجکت را بر می گرداند. تمام فراخوانی های متد سازنده، به همین آبجکت اشاره می کند.
جاوا اسکریپت بطور پیش فرض از پترن سینگل تون استفاده می کند اما نام آن object literal می باشد.
const user = { name: 'Peter', age: 25, job: 'Teacher', greet: function() { console.log('Hello!'); } };
به این دلیل که هر آبجکت یک فضای واحد و منحصربفرد را در حافظه اشغال می کند، وقتی آبجکت user را صدا می زنیم، در واقع داریم به آن آبجکت یک رفرنس می دهیم.
اگر ما بخواهیم متغیر user را کپی بگیریم و آن را به متغیر دوم اختصاص دهیم و متغیر دوم را تغییر دهیم، مثلا:
const user1 = user; user1.name = 'Mark';
در اینصورت مشاهده خواهیم کرد که مقدار هر دو متغیر تغییر می کند. زیرا آبجکت ها در javascript با رفرنس پاس داده می شوند و نه با مقدار. بنابراین با اینکه دو متغیر تعریف کرده ایم، اما در واقع هر دوی آنها به یک فضای واحد از حافظه اشاره می کنند و مقدار یکسانی خواهند داشت.
بعنوان مثال:
// prints 'Mark' console.log(user.name);// prints 'Mark' console.log(user1.name);// prints true console.log(user === user1);
پترن سینگلتون می تواند بوسیله تابع سازنده پیاده سازی شود:
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();// prints true console.log(user1 === user2);
وقتی این تابع سازنده (constructor) فراخوانی می شود، چک می کند که آیا نمونه ای از آبجکت وجود دارد یا خیر. اگر وجود نداشته باشد یک نمونه یا instance از آبجکت ایجاد می شود و کلمه this را به آن اختصاص می دهد. اما اگر نمونه از قبل تعریف شده باشد، آن آبجکت را بر می گرداند.
پترن سینگلتون همچنین می تواند توسط پترن ماژول پیاده سازی شود:
const singleton = (function() { let instance; function init() { return { name: 'Peter', age: 24, }; } return { getInstance: function() { if(!instance) { instance = init(); } return instance; } } })();const instanceA = singleton.getInstance(); const instanceB = singleton.getInstance();// prints true console.log(instanceA === instanceB);
در کد بالا، ما توسط متد singleton.getInstance وجود نمونه از آبجکت را چک کرده ایم. اگر نمونه از قبل وجود داشته باشد این متد خود این نمونه را بر می گرداند. و اما اگر نمونه ای از قبل ایجاد نشده باشد، بوسیله تابع init() آن را می سازد.
پترن فکتوری (Factory):
فکتوری پترن از متدهای فکتوری (factory methods) برای ایجاد آبجکت استفاده می کند. بدون مشخص کردن کلاس یا متد سازنده آن.
فکتوری پترن برای ایجاد آبجکت بدون در نظر گرفتن منطق instantiation بکار می رود. این نوع دیزاین پترن برای تولید آبجکت ها بر اساس شرایط خاص می تواند مفید باشد.
بعنوان مثال:
class Car{ constructor(options) { this.doors = options.doors || 4; this.state = options.state || 'brand new'; this.color = options.color || 'white'; } }class Truck { constructor(options) { this.doors = options.doors || 4; this.state = options.state || 'used'; this.color = options.color || 'black'; } }class VehicleFactory { createVehicle(options) { if(options.vehicleType === 'car') { return new Car(options); } else if(options.vehicleType === 'truck') { return new Truck(options); } } }
در این مثال ما دو کلاس Car و Truck با مقادیر پیش فرض داریم که برای تعریف دو آبجکت car و truck بکار می روند. کلاس VehicleFactory را به منظور ایجاد و return کردن یک شیء جدید بر اساس فیلد vehicleType تعریف کرده ایم:
const factory = new VehicleFactory();const car = factory.createVehicle({ vehicleType: 'car', doors: 4, color: 'silver', state: 'Brand New' });const truck= factory.createVehicle({ vehicleType: 'truck', doors: 2, color: 'white', state: 'used' });// Prints Car {doors: 4, state: "Brand New", color: "silver"} console.log(car);// Prints Truck {doors: 2, state: "used", color: "white"} console.log(truck);
در این مثال، ما یک آبجکت factory از کلاس VehicleFactory تعریف کرده ایم. بعد از آن ما می توانیم با فراخوانی factory.createVehicle آبجکت های Car و Truck ایجاد کنیم و آبجکت options را پاس بدهیم.
پترن دکوراتور (Decorator):
دکوراتور پترن برای توسعه قابلیت ها و امکانات یک آبجکت بدون تغییر کلاس یا متد سازنده فعلی آن بکار می رود. بعنوان یک مثال ساده برای دیزاین پترن دکوراتور داریم:
function Car(name) { this.name = name; // Default values this.color = 'White'; }// Creating a new Object to decorate const tesla= new Car('Tesla Model 3');// Decorating the object with new functionalitytesla.setColor = function(color) { this.color = color; }tesla.setPrice = function(price) { this.price = price; }tesla.setColor('black'); tesla.setPrice(49000);// prints black console.log(tesla.color);
اما بعنوان یک مثال واقعی سناریوی زیر را خواهیم داشت:
فرض کنید قیمت یک خودرو بر اساس تعداد امکاناتی که دارد متفاوت است. بدون استفاده از دیزاین پترن دکوراتور، ما مجبوریم برای هر یک از این خودروها یک کلاس مجزا تعریف کنیم. مثلا:
class Car() { }class CarWithAC() { }class CarWithAutoTransmission { }class CarWithPowerLocks { }class CarWithACandPowerLocks { }
اما بوسیله دیزاین پترن دکوراتور می توان یک کلاس پایه Car تعریف کرد که تنظیمات مربوط به قیمت های مختلف خودروها بر اساس امکانات آنها در آن پیاده سازی شده است:
class Car { constructor() { // Default Cost this.cost = function() { return 20000; } } }// Decorator function function carWithAC(car) { car.hasAC = true; const prevCost = car.cost(); car.cost = function() { return prevCost + 500; } }// Decorator function function carWithAutoTransmission(car) { car.hasAutoTransmission = true; const prevCost = car.cost(); car.cost = function() { return prevCost + 2000; } }// Decorator function function carWithPowerLocks(car) { car.hasPowerLocks = true; const prevCost = car.cost(); car.cost = function() { return prevCost + 500; } }
در کد فوق، ابتدا با هدف ایجاد آبجکت های Car ، یک کلاس پایه برای Car تعریف کرده ایم. سپس برای امکاناتی که می خواهیم به Car اضافه کنیم از دکوراتور استفاده کرده ایم. که بعنوان پارامترهای آبجکت Car به آن پاس داده می شوند. سپس بر طبق فیچرهای خودرو، فانکشن cost آپدیت می شود.
برای افزودن یک ویژگی جدید به خودرو می توان بصورت زیر عمل کرد:
const car = new Car(); console.log(car.cost());carWithAC(car); carWithAutoTransmission(car); carWithPowerLocks(car);
در نهایت می توان به شکل زیر قیمت نهایی خودرو را محاسبه کرد:
// Calculating total cost of the car console.log(car.cost());
جمع بندی:
در این مقاله چند دیزاین پترن کاربردی جاوا اسکریپت را معرفی کردیم. البته چند دیزاین پترن دیگر هم وجود دارد که ما در این مقاله به آنها نپرداختیم. همان اندازه که آشنایی با دیزاین پترن های مختلف مهم است، عدم استفاده بیش از حد از آنها نیز اهمیت دارد. قبل از استفاده از دیزاین پترن باید بررسی کنید که آیا پروژه شما و نیازمندیهای آن با امکانات دیزانی پترنی که انتخاب می کنید تطابق دارد یا خیر.
در جلسه 17 مفهوم کاربردی و مهم Promise در زبان برنامه نویسی جاوا اسکریپت را بطور کامل و مفهومی شرح خواهیم داد.
دیدگاهتان را بنویسید