الگوی طراحی Composite، یک الگوی طراحی ساختاری است که این امکان را به ما میدهد که آبجکتها را داخل یک ساختار درختی قرار دهیم و سپس با آن ساختار طوری کار کنیم که گویا یک آبجکت واحد است. این بدان معناست که استفاده از این الگوی طراحی، زمانی منطقی است که بخشی از برنامه ما قرار است به صورت درختی نمایش داده شود.
در این مقاله، ما قصد داریم نحوه پیاده سازی الگوی طراحی Composite و مزایای آن را یاد بگیریم.
این مقاله، بخشی از مجموعه آموزشی زیر است:
- الگوی طراحی Builder و Fluent Builder
- اینترفیس Fluent Builder به همراه Generic بازگشتی
- Facated Builder
- متد Factory
- Singleton
- Adapter
- Composite
- Decorator
- Command
- Strategy
- Facade
سورس کد در این لینک موجود است: الگوی طراحی Composite – سورس کد.
برای مشاهده لیست کامل مقالات این مجموعه آموزشی، الگوهای طراحی #C را بررسی کنید.
این مقاله شامل قسمتهای زیر میباشد:
ساختار الگوی طراحی Composite
الگوی طراحی Composite، شامل قسمتهای زیر میباشد:
- Component
- Leaf
- Composite
یک component، شامل عملیات مورد نیاز برای عناصر ساده و پیچیده یک ساختار درختی میباشد.
یک leaf، یک آبجکت واحد است که هیچ فرزندی ندارد. ساختار درختی ما، بیشتر شامل آبجکتهای leaf میباشد.
Composite، یک آبجکتی است که دارای فرزند (برگها یا دیگر اشیاء مرکب) میباشد. نکته جالب این است که آبجکت Composite با کلاس های واقعی فرزندان خود آشنایی ندارد. بلکه از طریق اینترفیس Component، با فرزندان خود ارتباط برقرار میکند.
در آخر، ما یک کلاینت داریم که از طریق اینترفیس Component، با تمام عناصر کار میکند.
و اما تئوری کافیست. اجازه دهید تا با یک مثال واقعی، کارمان را شروع کنیم.
پیاده سازی الگوی طراحی Composite
بیایید تصور کنیم که قیمت کل هدیه ای را که در مغازه خود می فروشیم را باید محاسبه کنیم. هدیه میتواند یک عنصر واحد (اسباب بازی) باشد یا میتواند یک عنصر پیچیده که شامل یک باکس باشد که درون آن باکس، دو اسباب بازی و یک باکس دیگر با شاید یک اسباب بازی داخل آن باشد. همانطور که اینجا متوجه شدیم، ما یک ساختار درختی داریم که ارائه دهنده هدیه complex ما است، بنابراین پیاده سازی الگوی طراحی Composite، راه حل درستی برای ما خواهد بود.
پس با قسمت Component
شروع میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public abstract class GiftBase { protected string name; protected int price; public GiftBase(string name, int price) { this.name = name; this.price = price; } public abstract int CalculateTotalPrice(); } |
ما اینجا میببینیم که component ما، شامل دو فیلد protected و یک متد abstract است. این فیلدها و متدها، قرار است که بین قسمتهای Leaf و Composite الگوی ما، مورد استفاده قرار بگیرند.
حالا در خیلی از مثالها، ما میتوانیم عملیات اضافی مانند add و remove را داخل کلاس abstract ببینیم. اما ما نمیخواهیم آنها را به این class اضافه کنیم. زیرا کلاس Leaf ما، به آنها نیازی ندارد. چیزی که ما در عوض قرار است ایجاد کنیم، یک اینترفیس جدید به نام IGiftOperations است:
1 2 3 4 5 |
public interface IGiftOperations { void Add(GiftBase gift); void Remove(GiftBase gift); } |
فقط آبجکت composite ما، این اینترفیس را پیاده سازی خواهد کرد، اما آبجکت leaf نیازی به پیاده سازی آن ندارد. این بسیار بهتر است زیرا آبجکت leaf ما نیازی به پیاده سازی متدهایی که از آن استفاده نمی کند، ندارد.
پیاده سازی کلاس Composite
بنابراین اجازه دهید تا با کلاس Composite
، کار خود را ادامه دهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
public class CompositeGift : GiftBase, IGiftOperations { private List<GiftBase> _gifts; public CompositeGift(string name, int price) :base(name, price) { _gifts = new List<GiftBase>(); } public void Add(GiftBase gift) { _gifts.Add(gift); } public void Remove(GiftBase gift) { _gifts.Remove(gift); } public override int CalculateTotalPrice() { int total = 0; Console.WriteLine($"{name} contains the following products with prices:"); foreach (var gift in _gifts) { total += gift.CalculateTotalPrice(); } return total; } } |
پیاده سازی این کلاس، بسیار واضح و ساده است. اول از همه، ما لیستی از نوع GiftBase
را داریم که در آن، آبجکتهای Leaf یا دیگر آبجکتهای Composite را ذخیره میکنیم. ما میتوانیم با پیاده سازی متدهای Add
و Remove
از اینترفیس IGiftOperations
، آبجکتها را به لیستمان، اضافه یا حذف کنیم. در آخر، ما میتوانیم قیمت کلی آبجکت Gift
خود را با تمام gift های موجود در آن محاسبه کنیم.
پیاده سازی کلاس Leaf
با قسمت leaf، کار خود را ادامه میدهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class SingleGift : GiftBase { public SingleGift(string name, int price) :base(name, price) { } public override int CalculateTotalPrice() { Console.WriteLine($"{name} with the price {price}"); return price; } } |
و این، تمام چیزی است که برای پیاده سازی Leaf
نیاز داریم، زیرا آن هیچ sub-level ای ندارد، بنابراین اصلا نیازی به ویژگیهای add یا remove ندارد.
در آخر، میتوانیم قسمت client خود را پیاده سازی کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Program { static void Main(string[] args) { var phone = new SingleGift("Phone", 256); phone.CalculateTotalPrice(); Console.WriteLine(); //composite gift var rootBox = new CompositeGift("RootBox", 0); var truckToy = new SingleGift("TruckToy", 289); var plainToy = new SingleGift("PlainToy", 587); rootBox.Add(truckToy); rootBox.Add(plainToy); var childBox = new CompositeGift("ChildBox", 0); var soldierToy = new SingleGift("SoldierToy", 200); childBox.Add(soldierToy); rootBox.Add(childBox); Console.WriteLine($"Total price of this composite present is: {rootBox.CalculateTotalPrice()}"); } } |
اینجا میبینیم که ما در حال ایجاد یک gift هستیم که فقط یک عنصر داخل آن وجود دارد و یک gift دیگر با ساختار پیچیده تر داریم که درون آن، اسباب بازیها و یک باکس اضافی دیگر با یک اسباب بازی داخل آن وجود دارد.
بنابراین، به محض اجرای برنامه خود، این نتیجه را خواهیم گرفت:
بسیار عالی. این مثال، الگوی طراحی Composite را پوشش داده است.
نتیجه گیری
اگرچه این الگو، مقداری پیچیده به نظر میرسد، با این حال استفاده از آن زمانیکه ما ساختار درختی پیچیده ای در کد خود داریم، خیلی مفید میباشد.
علاوه بر این، با داشتن یک اینترفیس single که توسط همه عناصر در الگوی Composite به اشتراک گذاشته می شود، client دیگر نیازی نیست در مورد کلاس واقعی که با آن کار می کند نگران باشد.