درود دوستان 🖐️ توی برنامه بارها شرایطی پیش میاد که می‌خوایم چند نمونه از یک کلاس داشته باشیم. شاید چیزی که به ذهنمون برسه این باشه که برای هر نمونه از کلمه کلیدی new استفاده کنیم:

class Cat {
  
}

const obj1 = new Cat();
const obj2 = new Cat();
const obj3 = new Cat();

اما گاهی ساختن نمونه به همین سادگی نیست و استفاده از new توی شرایط زیر بهینه نخواهد بود:

۱. ساختن نمونه از کلاس هزینه زیادی داره

مثلاً با هر new کردن از کلاس، اطلاعات زیادی باید از دیتابیس خونده و دسته‌بندی بشه:

class Post {
  constructor() {
    this.tags = db.tags.fetchAll();
  }
}

 

۲. آماده‌سازی نمونه زمان‌بر هست

مثلاً کلاسی داریم که نمونه‌های اون نیاز به پیکربندی‌های زیادی دارن:

class Car {
  public setWheels() {}
  public setDoors() {}
  public setWindows() {}
  public setSeats() {}
}

const car1 = new Car();
car1.setWheels();
car1.setDoors();
car1.setWindows();
car1.setSeats();

const car2 = new Car();
car2.setWheels();
car2.setDoors();
car2.setWindows();
car2.setSeats();

 

۳. کلاس واقعی برای ساختن نمونه ناشناخته هست

اگه از Dependency Injection استفاده می‌کنیم، کاملاً رایج هست که توی پارامترها از اینترفیسِ اون کلاس استفاده کنیم:

interface Animal {}

function produce(animal: Animal) {
  new Animal(); // Error: Cannot instantiate interface Animal
}

پس اینجا کلاس واقعی در دسترس نیست و نمی‌تونیم از اون نمونه‌ای بسازیم. توی چنین شرایطی راه حلی که به نظر مناسب میاد اینه که از خود پارامتر animal یک کپی بسازیم. برای این کار باید روی همه پراپرتی‌های آبجکت اصلی پیمایش کنیم و مقدارهای اونها رو به آبجکت جدید نسبت بدیم. ولی اگه آبجکت اصلی پراپرتی‌های private داشته باشه این کار ممکن نیست.
اینجاست که الگوی Prototype برای حل چنین مشکلاتی به کار ما میاد 👌

 

الگوی Prototype چیه؟

الگوی Prototype به ما این امکان رو میده تا با Clone (کپی) کردن از نمونه‌های موجود، نمونه‌های جدیدی بسازیم.

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

 

چه زمانی از الگوی Prototype استفاده کنیم؟

از این الگو زمانی استفاده می‌کنیم که می‌خوایم یک Clone (کپی) از یک نمونه داشته باشیم. اما ساختن نمونه با کلمه کلیدی new هزینه‌های زیادی داره و یا به نمونه مشابه یک نمونه دیگه لازم داریم، اما کلاس اصلی نمونه اصلی ناشناخته هست.

 

پیاده‌سازی الگوی Prototype

توی این مثال یک کلاس به اسم Book داریم:

class Book {
  private title;
  private price;
  private content;

  constructor(title, price) {
    this.title = title;
    this.price = price;
    this.content = this.fetchContentFromDb();
  }
  
  public fetchContentFromDb() {
    // const content = db.books.where('title', title).find().content;
    const content = "The book content";

    return content;
  }
}

const book1 = new Book('Funny JS', 36);
// ...
const book2 = new Book('Funny JS', 36);

پیاده‌سازی از این کلاس هزینه‌بردار هست. توی خط ۹ داریم محتویات کتاب که برای مثال شامل ۴۰۰ صفحه متن و عکس هست رو از دیتابیس می‌خونیم که اگه بخوایم مثل خط‌های ۲۰ و ۲۲ چندین نمونه به صورت new از این کلاس بسازیم منابع زیادی درگیر میشه که بهینه نخواهد بود. ولی می‌خوایم به کمک الگوی Prototype این هزینه رو کمتر کنیم و کدهای بهینه‌تری داشته باشیم.

ابتدا برای آبجکت‌هایی که قابلیت Clone شدن دارن یک اینترفیس درست می‌کنیم:

interface Prototype {
  clone();
}

حالا این اینترفیس رو به کلاس‌های مد نظرمون نسبت می‌دیم. متد clone رو جوری باید بنویسیم که بتونه یک نمونه مشابه رو تولید کنه و تحویل بده:

class Book implements Prototype {
  private title;
  private price;
  private content = null;

  constructor(title, price, content = null) {
    this.title = title;
    this.price = price;
    this.content = content !== null ? content : this.fetchContentFromDb();
  }
  
  public clone() {
    const content = this.content + ' (cached)';

    return new Book(this.title, this.price, content);
  }

  public fetchContentFromDb() {
    // const content = db.books.where('title', title).find().content;
    const content = "The book content";

    return content;
  }

  public getContent() {
    return this.content;
  }
}

متد سازنده (constructor) رو جوری نوشتیم که از content کش‌شده قبلی برای نمونه‌های جدید استفاده کنیم. بعد توی خط ۱۲ متد clone رو تعریف کردیم و توی اون یک نمونه از کلاس فعلی ساختیم این‌بار با content قبلی. اینطوری برای content نمونه‌های جدید لازم نیست اتصال به دیتابیس برقرار بشه و از همون نمونه‌های قبلی کپی میشه 👌

حالا وقتی اولین نمونه رو ساختیم، کافیه متد clone رو صدا بزنیم تا نمونه‌هایی مشابه نمونه اصلی داشته باشیم:

const original = new Book('Funny JS', 36);
const cloned = original.clone();

console.log(original.getContent()); // The book content
console.log(cloned.getContent());   // The book content (cached)

تموم. به همین سادگی تونستیم الگوی Prototype رو پیاده‌سازی کنیم! 😉

اگه از این مثال خوشتون اومد، کد کامل اون رو از ببینین و Star بزنین ⭐😉

 

مزایای الگوی Prototype

۱. با این الگو می‌تونیم از نمونه‌های موجود کپی داشته باشیم، بدون اینکه از کلاسِ نمونه اصلی آگاه باشیم. توی مثال بالا ما بدون آگاهی از نوع آبجکت original خیلی راحت از اون کپی گرفتیم

۲. نحوه آماده‌سازی آبجکتی که نیاز به طی‌کردن فرآیندهای طولانی داره از دید client (قسمت اصلی برنامه‌ی ما که از این الگو استفاده می‌کنه) مخفی میشه و اینطوری می‌تونیم خیلی راحت آبجکت‌های پیچیده‌تر رو بسازیم

۳. کدهای تمیزتر و قابل توسعه داریم. چون قسمت client لازم نیست کدهاش رو به کلاسی وابسته کنه که می‌خواد از اون نمونه بسازه (اصل پنجم سالید: Dependency Inversion)

 

معایب الگوی Prototype

۱. کپی گرفتن از یک آبجکت پیچیده با پراپرتی‌های زیاد و تو در تو کار دشوار و ظریفی خواهد بود

 

خب همراهان عزیز دیتی این هم از این قسمت. منتظر الگوهای بعدی باشین. روزاتون به سلامتی و خوشی 😉✌️

 

منابع: