بـازدیــدکــننده (Visitor)
هـــدف
الگوی بازدیدکننده (Visitor) یک الگوی طراحی رفتاری است که به شما اجازه میدهد الگوریتمها را از اشیایی که روی آنها عمل میکنند، جدا کنید.
مـسئـــلـه
تصور کنید که تیم شما یک برنامهای را توسعه میدهد که با اطلاعات جغرافیایی ساختار یافته به عنوان یک نمودار عظیم کار میکند. هر گره نمودار ممکن است یک موجودیت پیچیده مانند یک شهر را نشان دهد، اما همچنین چیزهای دقیقتر مانند صنایع، مناطق دیدنی و غیره را نشان دهد. گرهها با یکدیگر مرتبط هستند اگر بین اشیاء واقعی که نشان میدهند جادهای وجود داشته باشد. در زیر هر نوع گره توسط کلاس خاص خود نشان داده میشود، در حالی که هر گره خاص یک شیء است.
نمودار صادرات به XML
در برخی مواقع، شما وظیفه پیادهسازی صادرات نمودار به فرمت XML را داشتید. در ابتدا، این کار بسیار ساده به نظر میرسید. شما قصد داشتید یک متد صادرات به هر کلاس گره اضافه کنید و سپس از بازگشت برای پیمایش هر گره نمودار استفاده کنید و متد صادرات را اجرا کنید. این راهحل ساده و زیبا بود: به لطف چندشکلی، شما کد را به کلاسهای مشخص گرهها متصل نمیکردید.
متأسفانه، معمار سیستم از اجازه دادن به شما برای تغییر کلاسهای موجود گره خودداری کرد. او گفت که این کد قبلاً در تولید بود و نمیخواست به دلیل یک خطای احتمالی در تغییرات شما آن را خراب کند.
متد صادرات XML باید به تمام کلاسهای گره اضافه میشد که این خطر را داشت که در صورت لغزش هرگونه اشکال همراه با تغییر، کل برنامه را خراب کند.
علاوه بر این، او این سوال را مطرح کرد که آیا منطقی است که کد صادرات XML را در کلاسهای گره داشته باشیم. کار اصلی این کلاسها کار با دادههای جغرافیایی بود. رفتار صادرات XML در آنجا بیگانه به نظر میرسید.
دلیل دیگری برای این امتناع وجود داشت. این احتمال بسیار زیاد بود که پس از پیادهسازی این ویژگی، کسی از بخش بازاریابی از شما بخواهد که امکان صادرات به فرمت دیگری را فراهم کنید یا برخی موارد عجیب دیگر را درخواست کنید. این امر مجبور میکرد که دوباره این کلاسهای ارزشمند و شکننده را تغییر دهید.
راهــکــــار
الگوی بازدیدکننده پیشنهاد میکند که رفتار جدید را در یک کلاس جداگانه به نام بازدیدکننده قرار دهید، به جای اینکه سعی کنید آن را در کلاسهای موجود ادغام کنید. شیء اصلی که باید رفتار را انجام میداد اکنون به عنوان یک آرگومان به یکی از متدهای بازدیدکننده منتقل میشود و به این متد دسترسی به تمام دادههای ضروری موجود در شیء را میدهد.
اکنون، اگر آن رفتار بتواند بر روی اشیاء کلاسهای مختلف اجرا شود چه؟ برای مثال، در مورد ما با صادرات XML، پیادهسازی واقعی احتمالاً در کلاسهای مختلف گره کمی متفاوت خواهد بود. بنابراین، کلاس بازدیدکننده ممکن است نه یک، بلکه مجموعهای از متدها را تعریف کند که هر کدام میتوانند آرگومانهایی از انواع مختلف را بپذیرند، مانند این:
class ExportVisitor implements Visitor is
method doForCity(City c) { ... }
method doForIndustry(Industry f) { ... }
method doForSightSeeing(SightSeeing ss) { ... }
// ...
اما دقیقاً چگونه این متدها را فراخوانی میکنیم، بهخصوص هنگام برخورد با کل نمودار؟ این متدها امضاهای متفاوتی دارند، بنابراین نمیتوانیم از چندشکلی استفاده کنیم. برای انتخاب یک متد بازدیدکننده مناسب که بتواند یک شیء معین را پردازش کند، باید کلاس آن را بررسی کنیم. آیا این شبیه یک کابوس به نظر میرسد؟
foreach (Node node in graph)
if (node instanceof City)
exportVisitor.doForCity((City) node)
if (node instanceof Industry)
exportVisitor.doForIndustry((Industry) node)
// ...
}
ممکن است بپرسید، چرا از اضافه بار متد استفاده نمیکنیم؟ این زمانی است که به همه متدها یک نام یکسان میدهید، حتی اگر از مجموعههای مختلف پارامتر پشتیبانی کنند. متأسفانه، حتی با فرض اینکه زبان برنامهنویسی ما از آن پشتیبانی میکند (مانند جاوا و C#)، به ما کمک نخواهد کرد. از آنجایی که کلاس دقیق یک شیء گره از قبل ناشناخته است، مکانیسم اضافه بار نمیتواند متد صحیح را برای اجرا تعیین کند. این کار به صورت پیشفرض به متدی که یک شیء از کلاس پایه Node را میپذیرد، تبدیل میشود.
با این حال، الگوی بازدیدکننده این مشکل را برطرف میکند. این الگو از یک تکنیک به نام ارسال دوگانه استفاده میکند که به اجرای متد صحیح روی یک شیء بدون شرایط دست و پا گیر کمک میکند. به جای اینکه اجازه دهید مشتری نسخه مناسبی از متد را برای فراخوانی انتخاب کند، چگونه این انتخاب را به اشیایی که به عنوان یک آرگومان به بازدیدکننده منتقل میکنیم واگذار کنیم؟ از آنجایی که اشیاء کلاسهای خود را میشناسند، میتوانند به راحتی یک متد مناسب را روی بازدیدکننده انتخاب کنند. آنها یک بازدیدکننده را “پذیرفته” و به آن میگویند که کدام متد بازدید باید اجرا شود.
// Client code
foreach (Node node in graph)
node.accept(exportVisitor)
// City
class City is
method accept(Visitor v) is
v.doForCity(this)
// ...
// Industry
class Industry is
method accept(Visitor v) is
v.doForIndustry(this)
// ...
اعتراف میکنم. ما مجبور شدیم کلاسهای گره را تغییر دهیم. اما حداقل این تغییر جزئی است و به ما اجازه میدهد بدون تغییر مجدد کد، رفتارهای دیگری را اضافه کنیم.
اکنون، اگر یک رابط مشترک برای همه بازدیدکنندگان استخراج کنیم، تمام گرههای موجود میتوانند با هر بازدیدکنندهای که وارد برنامه میکنید کار کنند. اگر متوجه شدید که یک رفتار جدید مرتبط با گرهها را معرفی میکنید، تنها کاری که باید انجام دهید این است که یک کلاس بازدیدکننده جدید را پیادهسازی کنید.
مقایسه با دنیای واقعی
یک نماینده بیمه خوب همیشه آماده است تا انواع مختلفی از بیمه را به انواع مختلف سازمانها ارائه دهد.
تصور کنید یک نماینده بیمه باتجربه که مشتاق جذب مشتریان جدید است. او میتواند از هر ساختمانی در یک محله بازدید کند و سعی کند به هر کسی که ملاقات میکند بیمه بفروشد. بسته به نوع سازمانی که ساختمان را اشغال میکند، میتواند بیمههای تخصصی ارائه دهد:
اگر یک ساختمان مسکونی است، بیمه درمانی میفروشد.
اگر یک بانک است، بیمه سرقت میفروشد.
اگر یک کافیشاپ است، بیمه آتشسوزی و سیل میفروشد.
ســاخــتـــار
برای مطـالعـه متن کامل مقالــه، مجموعه کــــدها، نحوه پیاده سازی، مزایا و معایب و روابط با الگوهای دیگر، ایــنــجـــا کلیک کنید.