درود دوستان! می‌خوایم یکی از راحت‌ترین الگوهای طراحی رو بررسی کنیم. توی این قسمت یاد می‌گیریم که:

 

مشکل کجاست؟ 🤔

فرض کنیم یک برنامه مدیریت اعضای یک سازمان داریم که توی اون می‌خوایم مشخصات همه اعضای سازمان رو بدست بیاریم و نمایش بدیم. همونطور که می‌دونیم یک سازمان شامل بخش‌های مختلفی میشه و هر بخش هم می‌تونه زیربخش‌هایی داشته باشه:

|-- Organization
    |-- new Founder("Alex")
    |-- new HR("Morgan")
    |-- Officers
        |-- new CEO("John")
        |-- new CTO("Mario")
    |-- Employees
        |-- IT
            |-- Designers
                |-- new Designer("Sarah")
                |-- new Designer("John")
                |-- new Designer("Emily")
                |-- new Designer("Mario")

با توجه به ساختار بالا، چطوری می‌تونیم مشخصات همه اعضا رو بدست بیاریم؟ شاید چیزی که به ذهنمون میرسه این باشه که یک حلقه یا یک تابع بازگشتی بنویسیم:

organization.forEach(function(organ) {
  const output = [];

  if (organ instanceof Founder) {
>>    output.push(organ.details());
  }

  if (organ instanceof Officers) {
    organ.forEach(function(officer)) {
      if (officer instanceof CEO) {
>>        output.push(officer.information());
      }

      if (officer instanceof CTO) {
>>        output.push(officer.getDetails());
      }
    }

    // rest of the ugly code ...
  }
});

توی این کد:

۱. داریم روی همه اعضا پیمایش می‌کنیم
۲. وقتی به هر قسمت رسیدیم بررسی می‌کنیم که این قسمت از نوع کدوم کلاس هست
۳. با توجه به نوع کلاس بررسی می‌کنیم که آیا این عضو زیربخش‌هایی هم داره یا نه
۴. اگه زیربخشی وجود داشت، دوباره یک حلقه دیگه می‌زنیم
۵. اگه زیربخشی وجود نداشت، متد مربوط به گرفتن مشخصات عضو رو صدا می‌زنیم

این کد عجیب چند نکته داره:

  • ما باید کلاس و نوع همه اعضا رو بدونیم
  • ما باید متد مربوط به گرفتن مشخصات اعضا رو بدونیم (خط‌های ۵ و ۱۱ و ۱۵)
  • مشکل زمانی بیشتر میشه عمق‌های بیشتری به سازمان اضافه کنیم

همین موارد باعث میشه که کیفیت کد ما پایین بیاد و برنامه‌هایی غیرقابل توسعه بنویسیم. اما خب الگوها برای همین چیزها ساخته شدن 🤷‍♂️

الگویی که اینجا به کار ما میاد، الگوی Composite هست.

 

الگوی Composite چیه؟ 🤔

این الگو کمک می‌کنه تا بتونیم با گروهی از آبجکت‌ها که نوع‌های مختلفی دارن، اما باید به شکل یک ساختار درختی کنار هم قرار بگیرن، طوری کار کنیم که انگار همگی نوع‌های یکسانی دارن

الگوی Composite زیر مجموعه الگوهای Structural - الگوهایی که کمک می‌کنن تا آبجکت‌های موجود طوری با هم در تعامل باشن تا بتونیم ساختارهای بزرگتری طراحی کنیم - هست. این الگو که به اون Object Tree هم گفته میشه، بسیار ساده هست و پیاده‌سازی کاملاً راحتی داره.

 

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

توی پیاده‌سازی این الگو اینترفیس‌ها به کار ما میان تا با ساختن یک لایه انتزاعی بتونیم با همه آبجکت‌ها - بدون دونستن نوع اونها - کار کنیم. توی این الگو چندتا مفهوم وجود داره:
Leaf و Container

۱. Leaf یا برگ که به عضوی از ساختار درختی گفته میشه که هیچ زیرعضوی نداره. توی مثال بالا، Designer یک Leaf هست که هیچ زیربخشی نداره. به اون عضو ساده هم گفته میشه.

۲. Container که به عضوی گفته میشه که شامل زیربخش‌ها و همچنین Leaf هاست. به اون عضو مرکب یا Composite هم گفته میشه. توی مثال بالا Organization و Employees یک Container هستن که شامل عضوهای ساده و زیربخش‌ها میشن.

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

خب بریم این الگو رو برای مثالمون پیاده‌سازی کنیم.

 

مرحله اول: ساختن اینترفیس

ابتدا ما باید یک اینترفیس بسازیم و توی اون متدهایی رو تعریف کنیم که برای همه اعضا (ساده و مرکب) مشترک هست. اسم این اینترفیس رو می‌ذاریم Organ:

interface Organ {
  getInformation();
  getRevenue();
}

به این اینترفیس به قول معروف گفته میشه Component Interface.

 

مرحله دوم: ساختن کلاس برای اعضای ساده

اسم این کلاس رو می‌ذاریم SimpleOrgan. این کلاس باید از اینترفیس Organ تبعیت کنه:

class SimpleOrgan implements Organ {
  constructor(name) {
    this.name = name;
  }
  
  public getInformation() {
    return `My name is ${this.name}`;
  }

  public getRevenue() {}
}

 

مرحله سوم: ساختن کلاس برای اعضای مرکب

گفتیم که اعضای مرکب می‌تونن شامل اعضای ساده و همچنین اعضای مرکب دیگه باشن. اسم این کلاس رو می‌ذاریم CompoundOrgan به معنی عضو مرکب. این کلاس هم باید اینترفیس Organ رو پیاده‌سازی کنه:

class CompoundOrgan implements Organ {
  constructor(name) {
    this.name = name;
  }

  public getInformation() {
    return `This is ${this.name} organ`;
  }

  public getRevenue() {}
}

این کلاس چون شامل اعضای مرکب هست، ما باید به اون یک پراپرتی و یک متد اضافه کنیم برای اضافه کردن و نگهداری زیرعضوها:

class CompoundOrgan implements Organ {
>>   public children = []; // Array of organs
  // ...

>>   public add(organ: Organ) {
>>     this.children.push(organ);
>>   }

  public getInformation() {
    let output = `This is ${this.name} organ`;
    output += ` It contains ${this.children.length} members`;

    return output;
  }
}

نکته‌ای که توی کلاس‌های مرکب باید در نظر داشته باشیم اینه که این کلاس‌ها بیشتر کارشون رو محول می‌کنن به زیربخش‌های خودشون. پس باید متد getInformation رو کامل‌تر کنیم:

class CompoundOrgan implements Organ {
  // ...

  public getInformation() {
    let output = `This is ${this.name} organ`;
    output += ` It contains ${this.children.length} members`;
    
>>     this.children.forEach(organ => {
>>       output += organ.getInformation();
>>     });

    return output;
  }
}

توی این کد و توی خط ۸ روی زیربخش‌ها یک حلقه زدیم تا اطلاعات زیربخش‌ها رو هم داشته باشیم.

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

const organization = new CompoundOrgan('Main');
organization.add(new SimpleOrgan('Alex as Founder'));
organization.add(new SimpleOrgan('Morgan as HR'));

const officers = new CompoundOrgan('Officers');
officers.add(new SimpleOrgan('John as CEO'));
officers.add(new SimpleOrgan('John as CTO'));
organization.add(officers);

const employees = new CompoundOrgan('Employees');
const it = new CompoundOrgan('IT');
const designers = new CompoundOrgan('Desingers');
designers.add(new SimpleOrgan('Sarah as Desinger'));
designers.add(new SimpleOrgan('John as Desinger'));
designers.add(new SimpleOrgan('Emily as Desinger'));
designers.add(new SimpleOrgan('Mario as Desinger'));

it.add(designers);
employees.add(it);

organization.add(employees);

>> console.log(organization.getInformation());

توی این کد دقیقاً ساختار درختی سازمانی که ابتدای پست داشتیم رو پیاده‌سازی کردیم. ابتدا بخش‌های مرکب رو ساختیم و به اون زیربخش‌ها رو اضافه کردیم. برای گرفتن مشخصات همه اعضا، تنها کاری که باید انجام می‌دادیم، صدا زدن متد getInformation توی خط ۲۳ از بالاترین عضو بود 👌

خروجی که تولید میشه (با مقداری Indent برای خوانایی بهتر) به این صورت هست:

- This is Main organ. It contains 4 members 
  - My name is Alex as Founder
  - My name is Morgan as HR
  - This is Officers organ. It contains 2 members 
    - My name is John as CEO
    - My name is John as CTO
  - This is Employees organ. It contains 1 members 
    - This is IT organ. It contains 1 members 
  - This is Desingers organ. It contains 4 members 
    - My name is Sarah as Desinger
    - My name is John as Desinger
    - My name is Emily as Desinger
    - My name is Mario as Desinger

 

(Star هم بزنین 👋)

 

 

مزایای الگوی Composite

۱. این الگو به ما کمک می‌کنه تا خیلی راحت‌تر و بهتر با یک ساختار درختی کار کنیم

۲. می‌تونیم بی‌نهایت نوع از اعضا داشته باشیم. فقط کافیه اعضای جدید، اینترفیس Component رو پیاده‌سازی کنن (اصل دوم SOLID)

 

معایب الگوی Composite

اینترفیس Component ممکنه متدهایی رو ارائه بده که برای یک کدوم از اعضای ساده یا مرکب بی‌معنا و بدون کاربرد باشه (نقض شدن اصل چهارم SOLID)

 

خب دوستان، امیدوارم از این پست هم استفاده کرده باشین و بتونین این الگو رو توی برنامه‌هاتون به کار ببرین. روزتون خوش 😉✌️

 

منابع: