الگوی Builder در جاوا اسکریپت — از صفر تا صد

۴۸ بازدید
آخرین به‌روزرسانی: ۰۷ شهریور ۱۴۰۲
زمان مطالعه: ۴ دقیقه
الگوی Builder در جاوا اسکریپت — از صفر تا صد

هنگامی که اپلیکیشن‌هایی را در جاوا اسکریپت توسعه می‌دهید، ممکن است گاهی اوقات ساخت اشیایی که پیچیده هستند برایتان دشوار باشد. این حالت زمانی که اپلیکیشن بزرگ‌تر می‌شود، ابعاد پیچیده‌تری می‌یابد. خوشبختانه روش‌هایی برای تجزیه این پیچیدگی به گام‌های ساده و کوچک‌تر وجود دارد و این همان چیزی است که در این مقاله به توضیحش خواهیم پرداخت. در این مقاله در مورد الگوی Builder در جاوا اسکریپت صحبت می‌کنیم که به رفع این مشکلات کمک می‌کند.

الگوی طراحی Builder

الگوی طراحی که در این مقاله مورد بررسی قرار می‌دهیم معمولاً به نام الگوی طراحی Builder شناخته می‌شود. این الگو برای ساخت اشیای پیچیده مورد استفاده قرار می‌گیرد. این الگو به جداسازی ساخت شیء از نمایش آن کمک می‌کند و بدین ترتیب به استفاده مجدد از این کدها برای ایجاد بازنمایی‌های متفاوت کمک می‌کند.

این فرایند شامل مراحل زیر است:

  • کلاس پایه شامل منطق بیزینس است: همچنین شیئی را که ایجاد شده دریافت می‌کند و اقدام به تعیین مقادیر می‌کند.
  • جداسازی کدی که مسئول ایجاد اشیا در builder-ها است و در نهایت خود شیء یا کلاس است: همه این بیلدرها مسئول تعریف گام‌هایی خواهند بود که اشیای پیچیده را می‌سازند.
  • امکان استفاده از کلاس اختیاری به نام Director نیز وجود دارد. Director-ها شامل تعریف متدهایی هستند که تضمین می‌کنند گام‌ها برای ساخت اشیای رایج با ترتیب معینی اجرا می‌شوند.

الگوی Builder چه مشکلات دیگری را حل می‌کند؟

چنان که پیش‌تر اشاره کردیم، الگوی Builder عموماً در مواردی نیاز است که به دنبال روش تازه‌ای برای کمک به ساده‌سازی ساخت اشیای پیچیده هستیم. از این رو بهترین زمان برای استفاده از آن در کد مواردی است که با این مشکل مواجه می‌شویم یا این مشکل حاد می‌شود.

در ادامه به بررسی یک مثال کد می‌پردازیم. در مثال زیر بررسی کنید چرا مدیریت کد در آینده دشوار خواهد شد:

1class Frog {
2  constructor(name, weight, height, gender) {
3    this.name = name
4    this.weight = weight // in lbs
5    this.height = height // in inches
6    this.gender = gender
7  }
8  eat(target) {
9    console.log(`Eating target: ${target.name}`)
10  }
11}

در کد فوق یک کلاس Frog داریم. با نگاه کردن به این مثال چه مشکلاتی به ذهن شما می‌رسد؟ یک مشکل که ممکن است احتمالاً پیش بیاید، مربوط به پارامترهای آن است. منظور ما به طور دقیق خط زیر است:

1constructor(name, weight, height, gender) {

درک تعریف کلاس Frog آسان به نظر می‌رسد و چنان که می‌بینید خطوط کمی نوشته شده است. با این حال زمانی که تلاش کنیم وهله‌های Frog را مقداردهی کنیم، با مشکل متفاوتی مواجه می‌شویم:

1const bob = new Frog('Bob', 9, 2.2, 'male')

برای مثال تصور کنید به مدت شش ماه این پروژه را به دلایلی کنار می‌گذارید و سپس به آن مراجعه می‌کنید. پس از شش ماه به نظرتان از کجا خواهید دانست که آن پارامترها در میانه به چه چیزی اشاره می‌کنند؟ در این حالت باید به عقب بازگردید و سورس کد را بررسی کنید تا بتوانید به دقت درک کنید که معنی آن پارامترها چیست.

این وضعیت به طور خاص در حالتی که هر دو پارامتر از یک نوع باشند، بروز شدیدتری می‌یابد. هر توسعه‌دهنده‌ای به سادگی ممکن است موقعیت پارامترهای weight یا height را در زمان وهله‌سازی از یک Frog با هم اشتباه بگیرد. در سناریوهای دنیای واقعی، این موقعیت‌ها به طور خاص در صنایعی مانند بخش سلامت، مهم هستند، زیرا یک عدم مطابقت داده می‌تواند به طور بالقوه هزینه‌های سنگینی به شرکت وارد کند. اینک چنان که احتمالاً حدس می‌زنید با استفاده از الگوی Builder می‌توانیم مسئله را ساده‌تر کنیم. در ادامه کدی را می‌بینید که به وسیله این الگو ساده‌سازی شده است:

1class FrogBuilder {
2  constructor(name, gender) {
3    this.name = name
4    this.gender = gender
5  }
6  setWeight(weight) {
7    this.weight = weight
8    return this
9  }
10  setHeight(height) {
11    this.height = height
12    return this
13  }
14  build() {
15    if (!('weight' in this)) {
16      throw new Error('Weight is missing')
17    }
18    if (!('height' in this)) {
19      throw new Error('Height is missing')
20    }
21    return new Frog(this.name, this.weight, this.height, this.gender)
22  }
23}
24
25const leon = new FrogBuilder('Leon', 'male')
26  .setWeight(14)
27  .setHeight(3.7)
28  .build()

اکنون می‌توانیم به وضوح ببینیم که هنگام ایجاد وهله‌های Frog چه اتفاقی رخ می‌دهد.

در ادامه به بررسی سازنده می‌پردازیم و آنچه از این بازسازی به دست آمده را مورد بررسی دقیق قرار می‌دهیم:

1constructor(name, gender) {
2    this.name = name
3    this.gender = gender
4  }

FrogBuilder تعداد پارامترها را در زمان مقداردهی (وهله‌سازی) از 4 به 2 کاهش می‌دهد، زیرا آن‌ها را به جزییات پیاده‌سازی انتقال می‌دهد. این وضعیت نه‌تنها موجب ساده‌تر شدن درک آن‌ها می‌شود، بلکه در زمان وهله‌سازی نیز طبیعی‌تر به نظر می‌آیند:

1const sally = new FrogBuilder('Sally', 'female')

پیش‌تر دیدیم که به آسانی ممکن است فراموش کنیم کجا، چه زمان و چگونه مشخصه‌های weight و heightproperties را روی یک Frog تنظیم کرده‌ایم:

1const bob = new Frog('Bob', 9, 2.2, 'male')

با این رویکرد جدید، Builder (FrogBuilder) مشکل را از طریق تشویق به ارائه رویکردهای بازتر به مسئله حل می‌کند:

1const sally = new FrogBuilder('Sally', 'female')
2  .setWeight(5)
3  .setHeight(7.8)
4  .build()
5  
6console.log(sally)
7
8/*
9  Result: 
10    {
11      gender: "female",
12      height: 7.8,
13      name: "Sally",
14      weight: 5
15    }
16*/

به این ترتیب به همان نتیجه قبلی می‌رسیم و این بار کد با قابلیت مدیریت و خوانایی بیشتری تولید کرده‌ایم.

در این بخش به بررسی مثال دیگری می‌پردازیم. فرض کنید یک شیء پیچیده دارید که نیازمند یک وهله‌سازی بزرگ و گام به گام سیستماتیک از فیلدهای فراوان و شیءهای تودرتو است. این فرایند می‌تواند درون یک سازنده عظیم با ارامترهای زیاد صورت گیرد یا این که می‌توان آن را در سراسر کد پراکنده کرد.

برای نمونه تصور کنید یک شیء خودرو چگونه ساخته می‌شود. برای ساخت یک خودروی ساده باید ابتدا چهار چرخ بسازید، یک فرمان، ترمزها و پدال‌های گاز و ترمز نیز باید ساخته شوند. اما در صورتی که یک وانت با امکانات دیگر مانند سانروف و تهویه مطبوع بخواهیم چطور؟

ساده‌ترین راه‌حل به ظاهر این است که کلاس خودروی پایه را بسط دهیم و یک مجموعه کلاس‌های فرعی ایجاد کنیم که هر یک بخشی از پارامترها را پوشش دهند. اما در این حالت نیز در نهایت متوجه می‌شویم که هر بار که با پارامترهای جدیدی مانند پنجره‌های پرده‌دار مواجه می‌شویم، باید کلاس‌های فرعی جدیدی بسازیم و هر بار که این اتفاق می‌افتد، سلسله مراتب هر چه بیشتر رشد می‌یابد.

رویکرد دیگر شامل ایجاد یک سازنده عظیم است که درست در کلاس Base مربوط به خودرو عمل کرده و همه تغییرات پارامترها را که شیء خودرو را کنترل می‌کنند، مدیریت کند. با این که این کار موجب حذف نیاز به کلاس‌های فرعی می‌شود، اما باز هم یک مشکل جدید مطرح می‌شود.

هنگامی که یک سازنده عظیم دارید که تلاش می‌کند همه پارامترها را مدیریت کند، در اغلب موارد با عملکرد نامناسبی مواجه می‌شوید، زیرا بیشتر پارامترها در عمل مورد استفاده قرار نمی‌گیرند. بدین ترتیب حفظ انسجام و نگهداری بلوک سازنده به صورتی غیرضروری دشوار می‌شود. برای نمونه اغلب خودروها به سانروف نیاز ندارند و از این رو پارامترهای مرتبط با سانروف در 99% مواقع کاملاً بی‌استفاده می‌مانند.

بدین ترتیب به پایان این مقاله می‌رسیم. امیدواریم این راهنما را مفید و ارزشمند یافته باشید.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
better-programming
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *