الگوی Visitor

این الگو در مورد یک operation صحبت می کند که بر روی المان های یک structure باید اجرا شود. در نتیجه باید حتما یک structure موجود باشد که دارای المان های متفاوت باشد. visitor این امکان را می دهد که بدون ایجاد تغییر در المان ها، بتوانیم operationهای جدید تعریف کنیم. الگوی Visitorدر این الگو قصد داریم عملیات هایی را انجام دهیم که این عملیات ها به concrete classها وابستگی دارند و از طریق abstract class نمی توانیم آن ها را انجام دهیم. برای مثال در یک ساختار file system، می خواهیم بدونیم کدام folder بیشترین تعداد فایل را دارد یا کدام فایل بیشترین حجم را دارد.منظور از operations در اینجا، تعدادی عملیات بی ربط و متفاوت است که قرار است بر روی objectها کار کنند. مثال FileSystem در الگوی composite را در نظر بگیرید.یک visitor interface تعریف می کنیم که المان هایی که می خواهد را از ورودی دریافت می کند.public interface IFileSystemVisitor { public void Visit(Directory directory); public void Visit(File file); }با فرض بدست آوردن بیشترین حجم فایل، المان های این structure از طریق یک مکانیزم به این کلاس پاس داده می شوند.public class LargestFileVisitor : IFileSystemVisitor { private long _maxSize = 0; public virtual void Visit(Directory directory) { } public virtual void Visit(File file) { if (this._maxSize < file.Size()) this._maxSize = file.Size(); } public long GetMaximumSize() => _maxSize; }همچنین می توانیم visitorهای دیگری را برای مثال برای بدست آوردن بیشترین تعداد فایل در directory، عمق بیشتر، پیدا کردن یک نام در ساختار و غیره داشته باشیم.در نتیجه عملیات های مختلفی داریم که می خواهیم بر روی objectهای این ساختار اجرا شوند، بدون اینکه این عملیات ها را وارد objectها کنیم.در structure base class، یک متد اضافه می کنیم که یک visitor را از ما قبول می کند.public abstract class FileSystemItem { public abstract long Size(); public abstract void Accept(IFileSystemVisitor visitor); }در پیاده سازی ها، خود المان را به داخل visitor پاس می دهیم و از این طریق عملیات double dispatch را انجام می دهیم.public override void Accept(IFileSystemVisitor visitor) { visitor.Visit(this); }با این تفاوت که در پیاده سازی directory از آنجایی خودش یک composite است، باید تمام المان های آن هم accept شوند.public override void Accept(IFileSystemVisitor visitor) { visitor.Visit(this); foreach (var fileSystemItem in _children) fileSystemItem.Accept(visitor); }و با این روش استفاده می کنیم.var file1 = new File(); var file2 = new File(); var file3 = new File(); var innerDirectory = new Directory(file2, file3); var root = new Directory(file1, innerDirectory); var visitor = new LargestFileVisitor(); root.Accept(visitor); var maxSize = visitor.GetMaximumSize();این الگو به ما کمک می کند که براحتی بتوانیم در structureهای خود عملیات های متفاوتی را اضافه کنیم.برای مثال یک visitor دیگر را ببینیم که عملیات شمارش را انجام می دهد.public class CountingVisitor : IFileSystemVisitor { public int DirectoryCount { get; private set; } public int FileCount { get; private set; } public override void Visit(Directory directory) { DirectoryCount++; } public override void Visit(File file) { FileCount++; } }و از آن استفاده می کنیم.var visitor = new CountingVisitor(); var file1 = new File(); var file2 = new File(); var file3 = new File(); var innerDirectory = new Directory(file2, file3); var root = new Directory(file1, innerDirectory); root.Accept(visitor); var dirCount = visitor.DirectoryCount; var fileCount = visitor.FileCount;الگوی visitor می تواند هم باعث رعایت اصل OCP شود و هم می تواند آن را نقض کند. به عبارتی تغییرات می تواند از جهتی راحت باشد و از جهتی هم می تواند سخت باشد. اگر فضای تغییر را در دو زاویه operationها و elementها در نظر بگیریم، برای مثال operationهایی مثل LargestFile و Counting داشتیم و همچنین elementهایی مثل File و Directory داشتیم. گاهی operationها را توسعه داده می شوند و گاهی برای elementها می تواند این اتفاق بیافتد. الگوی visitor در حوزه توسعه operationها با رعایت اصل OCP به خوبی کار می کند اما در مورد elementها این اتفاق نمی افتد. برای اضافه کردن یک operation کافی است یک visitor جدید بنویسیم. اما برای اضافه کردن یک element جدید، با پیچیدگی روبرو می شویم. با این فرض که المان جدید را بخواهیم بر روی visitor interface اعمال کنیم، باید در تمام visitorها نیز اعمال شود.برای پیاده سازی راحت تر، می توانیم یک base class برای visitor خود ایجاد کنیم که دارای رفتارهای پیش فرض است و در پیاده سازی ها از آن inherit کنیم. به این دلیل که مجبور به پیاده سازی تمام رفتارها نشویم.public class FileSystemVisitor : IFileSystemVisitor { public virtual void Visit(Directory directory) { } public virtual void Visit(File file) { } }به طور خلاصه، operationهای جدید در این الگو، variability می باشند و المان ها و structure جزو commonality ما می باشند.