زمان تخمینی مطالعه: 11 دقیقه
الگوی Composite یک الگوی طراحی ساختاری(Structural) است که به شما امکان میدهد اشیاء را در ساختارهای درخت مانند ترکیب کنید تا با این ساختار سلسله مراتب ایجاد شده را که بخشی از کل است را نشان دهید. این الگو به مشتریان اجازه میدهد تا با اشیا و ترکیبات اشیاء به طور یکنواخت رفتار کنند. به عبارت دیگر، چه با یک شیء منفرد و چه با گروهی از اشیا (کامپوزیت)، مشتریان میتوانند به جای یکدیگر از آنها استفاده کنند. در واقع با این الگو میتوان پس از ایجاد سلسله مراتب، با این ساختارها طوری کار کرد که گویی اشیاء منفرد هستند.
بیان مسئله: استفاده از الگوی ترکیبی (Composite) تنها زمانی معنا دارد که مدل اصلی برنامه شما را بتوان به صورت درختی نشان داد. به عنوان مثال، تصور کنید که دو نوع شی Products و Boxes را دارید. در این جالت یک Boxes میتواند حاوی چندین Products و همچنین تعدادی Boxesکوچکتر باشد. این Boxes های کوچک هم میتوانند برخی از Products یا حتی Boxes های کوچکتر و غیره را نیز در خود جای دهند.
فرض کنید تصمیم دارید یک سیستم سفارش ایجاد کنید که از این کلاسها استفاده کند. سفارشها میتوانند شامل محصولات ساده و بدون بستهبندی، و همچنین جعبههای پر از محصولات و جعبههای دیگر باشند. چگونه قیمت کل چنین سفارشی را تعیین میکنید؟
برای حل این مشکل میتوانید روش مستقیم را امتحان کنید و تمام جعبهها را باز کنید، همه محصولات را مرور کنید و سپس هزینه کل را محاسبه کنید. این در دنیای واقعی قابل انجام است. اما در یک برنامه، به سادگی اجرای یک حلقه نیست. شما باید کلاسهای Products و Boxes که میگذرید، سطح تودرتو جعبهها و سایر جزئیات دیگر را بدانید. همه اینها باعث میشود که رویکرد مستقیم یا بیش از حد ناخوشایند یا حتی غیرممکن باشد.
الگوی Composite پیشنهاد میکند که از طریق یک اینترفیس مشترک با Productsو Boxes کار کنید که روشی را برای محاسبه قیمت کل تعریف میکند. خوب بیایید ببینیم این روش چگونه کار خواهد کرد؟ این الگو برای یک محصول، به سادگی قیمت محصول را برمیگرداند. برای یک جعبه، روی هر موردی که در جعبه وجود دارد میرود، قیمت آن را میپرسد و سپس کل این جعبه را برمیگرداند. اگر یکی از این اقلام یک جعبه کوچکتر بود، آن جعبه نیز شروع به مرور محتویات آن کرده و به همین ترتیب تا زمانی که قیمت تمام اجزای داخلی محاسبه شود. در این روش حتی یک جعبه میتواند مقداری هزینه اضافی مانند هزینه بستهبندی به قیمت نهایی اضافه کند.
بزرگترین مزیت این رویکرد این است که نیازی نیست به کلاسهای مشخصی از اشیاء که درخت را تشکیل میدهند اهمیت دهید. دانستن این که آیا یک شی یک محصول ساده است یا یک جعبه پیچیده لازم نیست. میتوانید از طریق اینترفیس مشترک با همه آنها یکسان رفتار کنید. همچنین زمانی که یک متد را فراخوانی میکنید، خود اشیا درخواست را به درخت ارسال میکنند.
نمونه قابل قیاس در دنیای واقعی
ارتش اکثر کشورها به صورت سلسله مراتبی ساختار یافتهاند. یک ارتش از چندین بخش تشکیل شده است. یک لشکر مجموعهای از تیپها است و یک تیپ شامل جوخههایی است که میتوانند به دستهها تقسیم شوند. در نهایت، یک دسته یک گروه کوچک از سربازان واقعی است. دستورات در بالای سلسله مراتب صادر میشود و به هر سطح منتقل میشود تا زمانی که هر سرباز بداند چه کاری باید انجام شود.
ساختار الگوی Composition
در این بخش به بررسی ساختار الگوی ترکیبی(Composition) میپردازیم و پیادهسازی آن را به صورت UML خواهیم دید.
- گام 1: اینترفیس Component عملیاتی را توصیف میکند که برای عناصر ساده و پیچیده درخت مشترک است.
- گام 2: Leaf یک عنصر اساسی یک درخت است که عناصر فرعی ندارد. معمولاً اجزای برگ بیشتر کار واقعی را انجام میدهند، زیرا کسی را ندارند که کار را به او واگذار کنند.
- گام 3: Container (معروف به کامپوزیت) عنصری است که دارای عناصر فرعی مانند برگ یا سایر کانتینرها است. یک کانتینر کلاسهای concrete فرزندانش را نمیشناسد. با تمام عناصر فرعی فقط از طریق اینترفیس کامپوننت کار میکند. پس از دریافت درخواست، یک کانتینر کار را به عناصر فرعی خود واگذار میکند، سپس نتایج میانی را پردازش کرده و نتیجه نهایی را به مشتری برمیگرداند.
- گام 4: کلاینت با تمام عناصر از طریق اینترفیس کامپوننت کار میکند. در نتیجه، مشتری میتواند با هر دو عنصر ساده یا پیچیده درخت به یک شکل کار کند.
شبه کد(Pseudocode)
در این مثال، الگوی Composite به شما امکان میدهد انباشته کردن اشکال هندسی را در یک ویرایشگر گرافیکی پیادهسازی کنید.
کلاس CompoundGraphic کانتینری است که میتواند شامل هر تعداد زیرشکل از جمله اشکال ترکیبی دیگر باشد. یک شکل مرکب همان متدهای یک شکل ساده را دارد. با این حال، به جای اینکه کاری را به تنهایی انجام دهد، یک شکل مرکب درخواست را به صورت بازگشتی به همه فرزندان خود ارسال میکند و نتیجه را «خلاصه» میکند. کد کلاینت با همه اشکال از طریق اینترفیس واحد مشترک برای همه کلاسهای شکل کار میکند. بنابراین، مشتری نمیداند که آیا با یک شکل ساده کار میکند یا یک شکل مرکب. مشتری میتواند با ساختارهای شی بسیار پیچیده کار کند، بدون اینکه به کلاسهای concrete که آن ساختار را تشکیل میدهند، وابسته شود.
// The component interface declares common operations for both
// simple and complex objects of a composition.
interface Graphic is
method move(x, y)
method draw()
// The leaf class represents end objects of a composition. A
// leaf object can't have any sub-objects. Usually, it's leaf
// objects that do the actual work, while composite objects only
// delegate to their sub-components.
class Dot implements Graphic is
field x, y
constructor Dot(x, y) { ... }
method move(x, y) is
this.x += x, this.y += y
method draw() is
// Draw a dot at X and Y.
// All component classes can extend other components.
class Circle extends Dot is
field radius
constructor Circle(x, y, radius) { ... }
method draw() is
// Draw a circle at X and Y with radius R.
// The composite class represents complex components that may
// have children. Composite objects usually delegate the actual
// work to their children and then "sum up" the result.
class CompoundGraphic implements Graphic is
field children: array of Graphic
// A composite object can add or remove other components
// (both simple or complex) to or from its child list.
method add(child: Graphic) is
// Add a child to the array of children.
method remove(child: Graphic) is
// Remove a child from the array of children.
method move(x, y) is
foreach (child in children) do
child.move(x, y)
// A composite executes its primary logic in a particular
// way. It traverses recursively through all its children,
// collecting and summing up their results. Since the
// composite's children pass these calls to their own
// children and so forth, the whole object tree is traversed
// as a result.
method draw() is
// 1. For each child component:
// - Draw the component.
// - Update the bounding rectangle.
// 2. Draw a dashed rectangle using the bounding
// coordinates.
// The client code works with all the components via their base
// interface. This way the client code can support simple leaf
// components as well as complex composites.
class ImageEditor is
field all: CompoundGraphic
method load() is
all = new CompoundGraphic()
all.add(new Dot(1, 2))
all.add(new Circle(5, 3, 10))
// ...
// Combine selected components into one complex composite
// component.
method groupSelected(components: array of Graphic) is
group = new CompoundGraphic()
foreach (component in components) do
group.add(component)
all.remove(component)
all.add(group)
// All components will be drawn.
all.draw()
قابلیتها و کاربردها
- هنگامی که باید یک ساختار شی درخت مانند را پیادهسازی کنید از الگوی Composite استفاده کنید. الگوی ترکیبی دو نوع عنصر اساسی را در اختیار شما قرار میدهد که یک اینترفیس مشترک دارند: برگهای ساده و کانتینرهای پیچیده. یک کانتینر میتواند هم از برگ و هم از کانتینر دیگر تشکیل شده باشد. این موضوع به شما امکان میدهد یک ساختار شی بازگشتی تودرتو که شبیه یک درخت است بسازید.
- زمانی که میخواهید کد کلاینت هر دو عنصر ساده و پیچیده به طور یکسان رفتار کند، از الگوی Composite استفاده کنید.همه عناصر تعریف شده توسط الگوی ترکیبی یک اینترفیس مشترک دارند. با استفاده از این اینترفیس، کلاینت نیازی به نگرانی در مورد کلاس concrete اشیایی که با آنها کار میکند، نخواهد بود.
نحوه پیادهسازی
- مطمئن شوید که مدل اصلی برنامه شما میتواند به عنوان یک ساختار درختی نمایش داده شود. سعی کنید آن را به عناصر و کانتینرها ساده تقسیم کنید. به یاد داشته باشید که کانتینرها باید بتوانند هم عناصر ساده و هم سایر کانتینرها را در خود داشته باشند.
- اینترفیس کامپوننت را با لیستی از متدهایی که برای اجزای ساده و پیچیده منطقی هستند، تعریف کنید.
- یک کلاس برگ برای نمایش عناصر ساده ایجاد کنید. یک برنامه ممکن است چندین کلاس برگ مختلف داشته باشد.
- یک کلاس کانتینر برای نمایش عناصر پیچیده ایجاد کنید. در این کلاس، یک فیلد آرایه برای ذخیره ارجاعات به عناصر فرعی قرار دهید. آرایه باید هم برگ و هم کانتینر را ذخیره کند، بنابراین مطمئن شوید که با نوع اینترفیس کامپوننت تعریف شده است. هنگام پیادهسازی متدهای اینترفیس کامپوننت، به یاد داشته باشید که یک کانتینر قرار است بیشتر کار را به عناصر فرعی واگذار کند.
- در نهایت متدهای افزودن و حذف عناصر فرزند در کانتینر را تعریف کنید. به خاطر داشته باشید که این عملیات را میتوان در اینترفیس کامپوننت تعریف کرد. این امر اصل جداسازی اینترفیس را نقض میکند زیرا متدها در کلاس برگ خالی خواهند بود. با این حال، مشتری قادر خواهد بود با همه عناصر به طور مساوی رفتار کند، حتی در هنگام ترکیب درخت.
مزایا و معایب الگوی Composite
این الگوی طراحی دارای مزایا و معایبی به شرح زیر است:
– مزایا
- میتوانید راحتتر با ساختارهای درختی پیچیده کار کنید: از چندریختی و بازگشت(recursion) به نفع خود استفاده کنید.
- اصل باز/بسته: شما میتوانید انواع عناصر جدید را بدون شکستن کد موجود وارد برنامه کنید، که اکنون با درخت شی کار میکند.
– معایب
- ممکن است فراهم ساختن یک اینترفیس مشترک برای کلاسهایی که عملکرد آنها بسیار متفاوت است دشوار باشد. در سناریوهای خاص، باید اینترفیس مؤلفه را بیش از حد تعمیم دهید و درک آن را سختتر کنید.
ارتباط با الگوهای دیگر
- میتوانید هنگام ایجاد درختهای مرکب پیچیده از Builder استفاده کنید زیرا میتوانید مراحل ساخت آن را طوری برنامهریزی کنید که به صورت بازگشتی کار کند.
- الگوی زنجیره مسئولیت(Chain of Responsibility) اغلب همراه با Composite استفاده میشود. در این حالت، هنگامی که یک جزء برگ درخواستی دریافت میکند، ممکن است آن را از طریق زنجیره تمام اجزای والد به ریشه درخت شی منتقل کند.
- شما میتوانید از الگوی Iterators برای عبور از درختان composite استفاده کنید.
- میتوانید از الگوی Visitor برای اجرای یک عملیات روی کل درخت composite استفاده کنید.
- میتوانید گرههای برگ مشترک درخت composite را به عنوان الگوی Flyweights پیادهسازی کنید تا مقداری RAM ذخیره کنید.
- الگوی Composite و Decorator نمودارهای ساختاری مشابهی دارند زیرا هر دو به ترکیب بازگشتی برای سازماندهی تعداد باز از اشیا متکی هستند. الگوی Decorator مانند Composite است اما فقط یک جزء فرزند دارد. یک تفاوت مهم دیگر این است که: Decorator مسئولیتهای بیشتری را به شی پوشیده شده اضافه میکند، در حالی که Composite فقط نتایج فرزندان خود را “جمعبندی” میکند. با این حال، الگوها همچنین میتوانند با هم همکاری کنند: به طور مثال میتوانید از Decorator برای گسترش رفتار یک شی خاص در درخت ترکیبی استفاده کنید.
- طرحهایی که به شدت از Composite و Decorator استفاده میکنند اغلب میتوانند از الگوی Prototype بهره ببرند. اعمال الگو به شما این امکان را میدهد که ساختارهای پیچیده را به جای بازسازی مجدد از ابتدا شبیهسازی کنید.