معرفی
الگوی ترکیبی راهی برای کار با ساختارهای درختی ارائه می دهد. به عبارت دیگر، ساختارهای داده با روابط والدین/فرزند. به عنوان مثال، JSON، HTML، XML. در چارچوب دات نت، XElement با استفاده از یک الگوی طراحی ترکیبی توسعه یافته است. می دانید زمانی که خالق آن را انجام می دهد مهم است.
یک مثال در دنیای واقعی می تواند سیستم فایل باشد.
همانطور که می بینید، دایرکتوری می تواند شامل چندین دایرکتوری یا فایل در آن باشد. با این حال، فایلها نمیتوانند حاوی فایلها یا فهرستهای بیشتری باشند زیرا به عنوان گرههای برگ در نظر گرفته میشوند. الگوی ترکیبی مشتریان را قادر میسازد تا با اشیا یا ترکیبات اشیاء به شکل یکنواخت تعامل داشته باشند. (ترکیب: دارای - یک رابطه) اکنون الگوی ترکیبی تنها در مورد مدل سازی ساختارهای درختی نیست، بلکه به طور خاص، مشتریان را قادر می سازد تا روی آن ساختارهای درختی به طور یکنواخت عمل کنند. بیایید با مثال ساختار فایل ما معنی آن را بفهمیم. اگر میخواهم اندازه یک فایل جداگانه را بدست بیاورم، میتوانم روی این فایل جداگانه روشی داشته باشم تا اندازه آن را به من بدهد و تعداد بایتهای موجود در فایل را به من برمیگرداند. اما اگر اندازه یک فهرست را بخواهم، مثلاً وبلاگ دایرکتوری، چه میشود؟ من فقط باید یک روش را در وبلاگ دایرکتوری و به عنوان یک مصرف کننده فراخوانی کنم،
از نظر مفهومی، این چیزی است که الگوی ترکیبی آن را قادر می سازد، چه من بخواهم بر روی سطح دانه ای یا یک گره برگ منفرد عمل کنم. الگوی ترکیبی ما را قادر می سازد تا با همه اینها به طور یکنواخت برخورد کنیم. به نمودار مفهومی نگاه کنید.
![](http://pezhvak24.ir/dl/codenevis/firstcode/article/composite-design-pattern/Images/Composite.jpg)
بیایید به سراغ کد برویم و یک برنامه کنسول هسته دات نت را برای مثال ساختار فایل خود توسعه دهیم. سعی می کنیم اندازه یک فایل یا یک دایرکتوری را بدست آوریم.
ابتدا یک کلاس جزء اضافه کنید
- namespace CompositeDesignPattern.FileSystem
- {
- public abstract class FileSystemItem
- {
- #region Properties
- public string Name { get; }
- #endregion
- #region Constructor
- public FileSystemItem(string name)
- {
- this.Name = name;
- }
- #endregion
- #region Methods
- public abstract decimal GetSizeinKB();
- #endregion
- }
- }
سپس، ما باید از گره برگ مراقبت کنیم.
- namespace CompositeDesignPattern.FileSystem
- {
- public class FileItem : FileSystemItem
- {
- #region Properties
- public long FileBytes { get; }
- #endregion
- #region COnstructor
- public FileItem(string name, long fileBytes) : base(name)
- {
- this.FileBytes = fileBytes;
- }
- #endregion
- public override decimal GetSizeinKB()
- {
- return decimal.Divide(this.FileBytes , 1000);
- }
- }
- }
در نهایت، بیایید جلو برویم و کلاس ترکیبی را اضافه کنیم.
- using System.Collections.Generic;
- using System.Linq;
- namespace CompositeDesignPattern.FileSystem
- {
- class Directory : FileSystemItem
- {
- #region Properties
- ///
- /// Here we are using 2 of c#-6's festures
- /// Readonly properties & auto property initializer
- ///
- public List childrens { get; } = new List();
- #endregion
- #region COnstructor
- public Directory(string name) : base(name)
- {
- }
- #endregion
- #region Methods
- public override decimal GetSizeinKB()
- {
- // Summarizing size of children
- return this.childrens.Sum(x => x.GetSizeinKB());
- }
- public void Add(FileSystemItem newNode)
- {
- this.childrens.Add(newNode);
- }
- public void Remove(FileSystemItem deleteNode)
- {
- this.childrens.Remove(deleteNode);
- }
- #endregion
- }
- }
در نهایت، کلاس تماس گیرنده ما:
- using CompositeDesignPattern.FileSystem;
- using CompositeDesignPattern.Structural;
- using System;
- namespace CompositeDesignPattern
- {
- class Program
- {
- //Client
- static void Main(string[] args)
- {
- //let's create a Directory
- var root = new Directory("Root");
- // Add 2 more folders
- var folder1 = new Directory("Folder1");
- var folder2 = new Directory("Folder2");
- // Add them under root directory
- root.Add(folder1);
- root.Add(folder2);
- //Add files to folder 1
- folder1.Add(new FileItem("MyBook.txt", 12000));
- folder1.Add(new FileItem("MyVideo.mkv", 1000000));
- //Add sub directory to folder 1
- var subfolder1 = new Directory("Sub Folder1");
- subfolder1.Add(new FileItem("MyMusic.mp3", 20000));
- subfolder1.Add(new FileItem("MyResume.pdf", 18000));
- folder1.Add(subfolder1);
- //Add files to folder 2
- folder2.Add(new FileItem("AndroidApp.apk", 250000));
- folder2.Add(new FileItem("WPFApp.exe", 87000000));
- Console.WriteLine($"Total size of (root): { root.GetSizeinKB() } KB");
- Console.WriteLine($"Total size of (folder 1): { folder1.GetSizeinKB() }KB");
- Console.WriteLine($"Total size of (folder 2): { folder2.GetSizeinKB() }KB");
- }
- }
- }
همانطور که می بینید، تماس گیرنده فقط باید تابع GetSizeinKB() را فراخوانی کند تا اندازه هر دایرکتوری یا فایلی را بدست آورد.
حالا برنامه خود را اجرا کنید و خروجی را بررسی کنید... عالی کار می کند.
همه چیز به خوبی کار می کند، به جز اینکه کلاس تماس گیرنده بسیار به هم ریخته است. ما در حال جمع کردن فایل ها بدون ترتیب مناسب هستیم، به علاوه هر بار که باید یک دایرکتوری یا فایل را مقداردهی اولیه کنیم، باید از یک کلمه کلیدی جدید استفاده کنیم که بسیار نامرتب به نظر می رسد.
کاری که می توانیم انجام دهیم این است که می توانیم یک کلاس سازنده در وسط اضافه کنیم. در اینجا نحوه طراحی الگوی سازنده آورده شده است:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- namespace CompositeDesignPattern.FileSystem
- {
- public class FileSystemBuilder
- {
- internal Directory Root { get; }
- internal Directory CurrentDirectory { get; set; }
- #region Properties
- #endregion
- #region Constructor
- public FileSystemBuilder(string rootDirectory)
- {
- // Notice we've already encapsulated the instantiation of the object.
- this.Root = new Directory(rootDirectory);
- this.CurrentDirectory = Root;
- }
- internal FileItem AddFile(string name, int size)
- {
- var file = new FileItem(name, size);
- CurrentDirectory.Add(file);
- return file;
- }
- internal Directory SetCurrentDirectory(string currentDirName)
- {
- var dirStack = new Stack();
- dirStack.Push(this.Root);
- while (dirStack.Any())
- {
- //topmost element
- var currentDir = dirStack.Pop();
- if(currentDirName == currentDir.Name)
- {
- this.CurrentDirectory = currentDir;
- return currentDir;
- }
- //iterate through childrens
- //As directory can have file and directory both we have specify what are we iterating
- foreach (var item in currentDir.childrens.OfType())
- {
- //we are inserting childrens of the root and it will check for its name in code up above
- dirStack.Push(item);
- }
- }
- //let the user know that he is looking for wring directory.
- throw new InvalidOperationException($"Directory name: '{ currentDirName }' not found!");
- }
- internal Directory AddDirectory(string name)
- {
- var dir = new Directory(name);
- this.CurrentDirectory.Add(dir);
- this.CurrentDirectory = dir;
- return dir;
- }
- #endregion
- }
- }
همانطور که می بینید، ما کامپوزیت را در کلاس Builder CompositeDesignPattern خود کپسوله کرده ایم.
همچنین توجه داشته باشید که در روش ما SetCurrentDirectory(). ما یک بازگشتی برای جستجوی یک دایرکتوری انجام نمی دهیم، زیرا اگر دایرکتوری به اندازه کافی بزرگ باشد، راه حل بهینه نیست. در عوض، ما از یک راه حل تکراری مبتنی بر پشته استفاده کرده ایم.