مقدمه

در درس‌های گذشته با مفاهیم انعکاس (Reflection) و صفت‌ها (Attributes) آشنا شدیم. اکنون زمان آن رسیده که این مفاهیم قدرتمند را با هم ترکیب کرده و یکی از جذاب‌ترین کاربردهای آن‌ها را پیاده‌سازی کنیم: یک اپلیکیشن قابل توسعه (Extensible Application) یا پلاگین‌محور. ایده‌ی اصلی این است که یک برنامه‌ی هسته (host) داشته باشیم که بتواند در زمان اجرا، قابلیت‌های جدیدی را از طریق اسمبلی‌های خارجی (پلاگین‌ها) کشف و بارگذاری کند، بدون اینکه در زمان کامپایل هیچ اطلاعی از آن‌ها داشته باشد. این معماری به ما و دیگر توسعه‌دهندگان اجازه می‌دهد تا بدون نیاز به تغییر یا کامپایل مجدد برنامه‌ی اصلی، قابلیت‌های جدیدی به آن اضافه کنیم.

طراحی معماری پلاگین

سیستم ما از سه بخش اصلی تشکیل خواهد شد:

  1. یک کتابخانه‌ی کلاس مشترک: این کتابخانه شامل قراردادها (اینترفیس‌ها و صفت‌ها) است که هم برنامه‌ی هسته و هم تمام پلاگین‌ها به آن ارجاع خواهند داد.
  2. برنامه‌ی هسته (Host Application): یک اپلیکیشن کنسول که مسئول پیدا کردن، بارگذاری و اجرای پلاگین‌هاست.
  3. یک یا چند پلاگین (Plugin): هر پلاگین یک کتابخانه‌ی کلاس جداگانه است که قراردادهای تعریف‌شده در کتابخانه‌ی مشترک را پیاده‌سازی کرده و یک قابلیت مشخص را ارائه می‌دهد.

قدم اول: تعریف قرارداد مشترک

ابتدا یک پروژه‌ی جدید از نوع Class Library به نام MyApp.Contracts می‌سازیم. این پروژه شامل یک اینترفیس برای پلاگین‌ها و یک صفت سفارشی برای علامت‌گذاری آن‌ها خواهد بود.

Copy Icon IPlugin.cs
namespace MyApp.Contracts
{
    public interface IPlugin
    {
        string Name { get; }
        void Execute();
    }
}
Copy Icon PluginAttribute.cs
namespace MyApp.Contracts
{
    [AttributeUsage(AttributeTargets.Class)]
    public class PluginAttribute : Attribute { }
}

صفت AttributeUsage به کامپایلر می‌گوید که این صفت (PluginAttribute) فقط می‌تواند بر روی کلاس‌ها اعمال شود.

قدم دوم: ساخت پلاگین‌ها

حالا دو کتابخانه‌ی کلاس جداگانه به نام‌های HelloPlugin و GoodbyePlugin می‌سازیم. هر دوی این پروژه‌ها باید به پروژه‌ی MyApp.Contracts ارجاع دهند.

Copy Icon HelloPlugin.cs
using MyApp.Contracts;

[Plugin]
public class HelloPlugin : IPlugin
{
    public string Name => "Hello Plugin";
    public void Execute() => Console.WriteLine("Hello, World!");
}

پلاگین دوم را نیز به شکل مشابهی می‌سازیم. هر پلاگین اینترفیس IPlugin را پیاده‌سازی کرده و با صفت [Plugin] علامت‌گذاری شده است.

قدم سوم: ساخت برنامه‌ی هسته (Host)

نهایتاً، یک اپلیکیشن کنسول به نام MyApp.Host می‌سازیم. این پروژه نیز باید به MyApp.Contracts ارجاع دهد. منطق اصلی این برنامه، جستجو در یک پوشه‌ی مشخص (مثلاً Plugins)، پیدا کردن تمام فایل‌های .dll، بارگذاری آن‌ها، و سپس بازرسی انواع موجود در آن‌ها برای پیدا کردن کلاس‌هایی است که قرارداد ما (یعنی پیاده‌سازی IPlugin و داشتن صفت [Plugin]) را رعایت کرده‌اند.

Copy Icon Program.cs
using MyApp.Contracts;
using System.Reflection;

var plugins = new List<IPlugin>();
string pluginPath = Path.Combine(AppContext.BaseDirectory, "Plugins");

// 1. Load assemblies from the "Plugins" folder.
foreach (string dll in Directory.GetFiles(pluginPath, "*.dll"))
{
    Assembly pluginAssembly = Assembly.LoadFrom(dll);
    
    // 2. Inspect each type in the assembly.
    foreach (Type type in pluginAssembly.GetTypes())
    {
        // 3. Check if the type implements IPlugin and has the [Plugin] attribute.
        if (typeof(IPlugin).IsAssignableFrom(type) && type.GetCustomAttribute<PluginAttribute>() != null)
        {
            // 4. If it matches, create an instance and add it to our list.
            IPlugin pluginInstance = (IPlugin)Activator.CreateInstance(type);
            plugins.Add(pluginInstance);
        }
    }
}

// 5. Now, use the discovered plugins.
Console.WriteLine("--- Loaded Plugins ---");
foreach (IPlugin plugin in plugins)
{
    Console.WriteLine($"Executing '{plugin.Name}'...");
    plugin.Execute();
}

برای اجرای این برنامه، باید پس از کامپایل، فایل‌های HelloPlugin.dll و GoodbyePlugin.dll را در یک پوشه‌ی Plugins در کنار فایل اجرایی MyApp.Host.exe قرار دهیم. برنامه به طور خودکار آن‌ها را پیدا، بارگذاری و اجرا خواهد کرد. ما توانستیم قابلیت‌های جدیدی را به برنامه اضافه کنیم، بدون اینکه حتی یک خط از کد برنامه‌ی هسته را تغییر دهیم. این قدرت واقعی یک معماری قابل توسعه است.