PDA

View Full Version : آموزش: Dynamic Dispatch یا اعزام پویا در C#



piroozman
دوشنبه 07 تیر 1395, 14:43 عصر
امروز قصد دارم یکی از مفاهیم مفید زبانهای پویا با نام Dynamic Dispatch یا اعزام پویا را برای شما دوستان تشریح کنم و همچنین روشهای مختلف پیاده سازی آنرا در C#‎‎‎‎‎‎‎‎‎ برای شما تشریح خواهم نمود.
ابهامات بسیاری درباره کلماتی شبیه dispatch، binding و typing وجود دارد، بنابراین فکر می کنم بهتر است ابتدا ببینیم مفهوم Dispatch یا اعزام چیست. به عبارت ساده، اعزام روشی است که زبان برنامه نویسی یک متد یا تابع را فراخوانی می‌کند و در کل مهم نیست که یک متد به کدام نمونه (instance) یا یک کلاس تعلق دارد.
دو نوع اعزام وجود دارد. یکی به صورت ایستا یا Static و دیگری به صورت پویا یا Dynamic. مفهوم اولی این است که هر متد در زمان کامپایل شناخته می شود. متدهای اعزامی به صورت پویا در زمان اجرا و بر اساس نوع پارامترهای آن تعیین می شوند.
همچنین، زبانهای اعزامی پویا را می توان به دو گروه تقسیم نمود: اعزام تکی (single) یا چندتایی (multiple). در اعزام تکی فقط یک پارامتر برای انتخاب یک متد ارسال می‌شود. به عبارت دیگر، در اعزام چندتایی این مزیت وجود دارد که شما می‌توانید تعداد بیشتری پارامتر برای انتخاب یک تابع یا متد ارسال کنید.
زبانهای بسیاری هستند که از اعزام تکی پشتیبانی می‌کنند و سی شارپ نیز یکی از آنهاست تا این که نسخه 4.0 آن رسید. معمولاً این نوع از اعزام با استفاده از مدل وراثت و متدهای مجازی (virtual) پیاده سازی می‌شوند. شما از کلاس پدر ارث بری می‌کنید، یک متد را override (یا باطل) می کنید و در زمان اجرا و بر اساس نوع شی جاری، متد را فراخوانی می‌کنید.
اما آیا روش دیگری هم وجود دارد که با استفاده از آنها بتوان از مزایای اعزام پویا در سی شارپ استفاده کرد؟
فرض کنیم که چند کلاس در یک شی وراثتی ساده داریم که به هر دلیل نمی‌توان کدی را به آنها اضافه کرد (فرض کنید کدها برای خودمان نیست).
public interface IBar {}
public class Bar : IBar {}
public sealed class FooBar : Bar {}

// Simple helper for demonstration
public static class ConsolePrinter
{
public static void Print(IBar item)
{
Console.WriteLine("IBar");
}

public static void Print(Bar item)
{
Console.WriteLine("Bar");
}

public static void Print(FooBar item)
{
Console.WriteLine("FooBar");
}
}

برای هر نوع قصد داریم یک متد جداگانه بر اساس نوع زمان اجرای شی فراخوانی کنیم:
var bar = new Bar();
var foo = new FooBar();
IBar ibar = new FooBar();

ConsolePrinter.Print(bar);
ConsolePrinter.Print(foo);
ConsolePrinter.Print(ibar);

// prints Bar
// prints FooBar
// prints IBar

در کد بالا، اعزام ایستا این اطمینان را ایجاد می کند که برای هر فراخوانی یک متد با امضای مناسب فراخوانی شود. این کار برای اشیاء bar و foo خوب است اما برای ibar متد به جای فراخوانی متدی با امضای نوع FootBar، امضای interface را فراخوانی می کند.
وقتی که این اشیاء را درون یک آرایه قرار دهید، اوضاع بدتر هم خواهد شد:
IBar[] items = { bar, foo, ibar };
foreach (var item in items)
{
ConsolePrinter.Print(item);
}

// prints IBar
// prints IBar
// prints IBar

این یک مثال ساختگی بود، اما تصور کنید که خصیصه ای در یک کلاس دارید که دارای آرایه‌ای از IBar ها است یا آنها به عنوان یک ورودی به یک متد به کارگرفته شده باشند.
می‌خواهیم که خروجی Bar و FooBar باشد و اعزام ایستا اینجا کار نمی‌کند. چکار کنیم؟
کلمه کلیدی Dynamic
کلمه کلیدی dynamic در سی شارپ نسخه 4 برای تسهیل تعامل با اشیاء COM و زبانهای پویای CLR مانند IronPython و IronRuby معرفی شد. همچنین، در پروژه های ASP.NET MVC برای عبور دادن View ها و تعامل با آنها در الگوهای Razor استفاده می شود.
این کلمه کلیدی اعزام چندتایی پویای صحیح را به سی شارپ به ارمغان آورد و حتی بیشتر، قدرت بیان را به C#‎‎‎‎‎‎‎‎‎ اعطا کرد.
foreach (dynamic item in items)
{
ConsolePrinter.Print(item);
}

// prints Bar
// prints FooBar
// prints FooBar

با یک تغییر ساده توانستیم اعزام استاتیک یک قطعه کد را به یک اعزام پویا تبدیل کنیم. اما به یاد داشته باشید که قدرت، مسئولیت به همراه دارد و شما باید بدانید که چه وقت از آن استفاده کنید.
عیب استفاده از این کلمه کلیدی گسترش یافتن مجموعه کاری خواهد بود، بنابراین ممکن است کارایی کاهش پیدا کند. در ضمن این کلمه کلیدی برای سایر مقاصد طراحی شده است، پس از آن فقط برای ساده سازی کدهای بزرگ استفاده کنید.
به شخصه، توصیه می‌کنم از کلمه کلید dynamic در تجزیه کننده ها (parsers)، utilities، آزمونها و همه مواردی که این کلمه کلیدی برای آن طراحی شده است استفاده کنید.
Dictionary
به غیر از کلمه کلید dynamic، روش دیگری برای دستیابی به این رفتار وجود دارد و آن استفاده از یک دیکشنری ساده است.
var actions = new Dictionary<Type, Action<IBar>>
{
{typeof (IBar), ConsolePrinter.Print},
{typeof (Bar), i => ConsolePrinter.Print((Bar)i)},
{typeof (FooBar), i => ConsolePrinter.Print((FooBar)i)}
};

foreach (var item in items)
{
actions[item.GetType()](item);
}

احتمالاً این کد بهتر از داشتن تعداد زیادی دستورات if هست، اما می تواند به عنوان یک ضد الگو نیز باشد پس اجازه ندهید که دیکشنری شما خیلی زیاد رشد کند.
اما بر خلاف الگوی visitor که در ادامه به آن هم خواهیم رسید، لازم نیست انواع خود را برای این که آن کار کند تغییر دهید و این کار را می‌توانید در نسخه‌های قبلی سی شارپ که هنوز کلمه کلید dynamic معرفی نشده بود، انجام دهید.
Visitor
روش کلاسیک حل مسئله اعزام پویا استفاده از الگوی Visitor است. به هر حال، نیاز است که انواع خود را یک کمی تغییر بدهید و یک کلاس و یک اینترفیس جدید ایجاد کنید.
public interface IBar
{
void Accept(IBarVisitor visitor);
}

public class Bar : IBar
{
public virtual void Accept(IBarVisitor visitor)
{
visitor.Visit(this);
}
}

public sealed class FooBar : Bar
{
public override void Accept(IBarVisitor visitor)
{
visitor.Visit(this);
}
}

public interface IBarVisitor
{
void Visit(FooBar item);
void Visit(Bar item);
}

public sealed class PrintBarVisitor : IBarVisitor
{
public void Visit(FooBar item)
{
ConsolePrinter.Print(item);
}

public void Visit(Bar item)
{
ConsolePrinter.Print(item);
}
}

دقت کنید که متد Visit از نوع مجازی (virtual) باشد و مطمئن شوید که آن را در همه فرزندان به صورت override در بیاورید، در غیر اینصورت آن کار نخواهد کرد.
استفاده از این الگو به شکل زیر است:
var visitor = new PrintBarVisitor();

foreach (var item in items)
{
item.Accept(visitor);
}

همانطور که قبلاً اشاره شد، باید انواع خود را تغییر دهید و موقعیتهایی وجود دارد که نمیخواهید یا نمی توانید انواع را تغییر دهید.
مجبور شدید برای یک چیز ساده کلی کد نویسی کنید و وقتی که لازم باشد از اعزامهای چندتایی پشتیبانی کنید با این روش کدهای شما آشفته تر و کثیف تر هم خواهد شد .
نکته خوب در مورد visitor این هست که visitor یک الگوی بسیار ماژولار بوده و وقتی که می خواهید قابلیت های مختلفی را در visitorهای بیشتری کپسوله کنید آن نیازهای شما را برآورده خواهد کرد.
به طور خلاصه، C#‎‎‎‎‎‎‎‎‎ گزینه های بسیاری را برای رسیدگی به اعزامهای پویا پیشنهاد می کند. سی شارپ از نظر پویایی بسیار قدرتمند بوده و می‌توانید بهترین گزینه را انتخاب کنید.
منبع: https://chodounsky.net/2014/01/29/dynamic-dispatch-in-c-number