الگوی طراحی آداپتور (Adapter Design Pattern)

...
الگوی طراحی آداپتور (Adapter Design Pattern)

1 ماه پیش توسعه نرم افزار

الگوی طراحی آداپتور یک الگوی ساختاری است که به عنوان پلی بین دو رابط ناسازگار عمل می‌کند و به آنها امکان می‌دهد با هم کار کنند. این الگو به ویژه برای ادغام کد قدیمی یا کتابخانه‌های شخص ثالث در یک سیستم جدید مفید است.

بیایید این موضوع را با کمک نموداری که در ابتدای صفحه مشاهده نمودید، درک کنیم:

کلاینت می‌خواهد از یک رابط Target استفاده کند (()Request را فراخوانی می‌کند).

Adaptee از قبل قابلیت‌های مفیدی دارد، اما متد آن (()SpecificRequest) با رابط Target مطابقت ندارد.

Adaptee به عنوان یک پل عمل می‌کند: رابط Target (Request()) را پیاده‌سازی می‌کند، اما در داخل، ()SpecificRequest مربوط به Adaptee را فراخوانی می‌کند.

این موضوع به کلاینت اجازه می‌دهد تا بدون تغییر کد آن، از Adaptee استفاده کند.

مثالی از الگوی طراحی آداپتور در دنیای واقعی

الگوی طراحی آداپتور را می‌توان به عنوان یک آداپتور موبایل در نظر گرفت که به تطبیق نیازهای برق و ولتاژ دستگاه کمک می‌کند. به طور مشابه در توسعه نرم‌افزار، ما از آداپتور برای فعال کردن قابلیت همکاری بین رابط‌های ناسازگار استفاده می‌کنیم.

  • نرم‌افزارهایی که از فرمت‌های فایل مختلف (مانند CSV، JSON، XML) استفاده می‌کنند، از آداپتورها برای تبدیل این فرمت‌ها به فرمتی که برنامه می‌تواند با آن کار کند، استفاده می‌کنند و قابلیت همکاری داده‌ها را تسهیل می‌کنند.
  • درایورهای دستگاه در سیستم‌های عامل، رابط‌های پایگاه داده و مبدل‌های زبان (چینی به انگلیسی و انگلیسی به هندی) نمونه‌های بیشتری از آداپتورها هستند.
  • در شرایطی که تغییر عمده‌ای در APIهای جدید در مقابل قدیمی داریم، به جای نوشتن مجدد کل کد قدیمی، می‌توانیم از آداپتوری استفاده کنیم که تبدیل را انجام می‌دهد.

مزایای الگوی طراحی آداپتور

در زیر مزایای الگوی طراحی آداپتور آمده است:

  • استفاده مجدد از کد را بدون تغییر ترویج می‌دهد.
  • با جداسازی سازگاری، کلاس‌ها را بر منطق اصلی متمرکز نگه می‌دارد.
  • از طریق آداپتورهای قابل تعویض، از رابط‌های چندگانه پشتیبانی می‌کند.
  • سیستم را از پیاده‌سازی‌ها جدا می‌کند و تغییرات و تعویض‌ها را آسان می‌کند.

معایب الگوی طراحی آداپتور

در زیر معایب الگوی طراحی آداپتور آمده است:

  • پیچیدگی ایجاد می‌کند و می‌تواند دنبال کردن کد را دشوارتر کند.
  • به دلیل غیرمستقیم بودن اضافی، سربار عملکردی کمی ایجاد می‌کند.
  • آداپتورهای متعدد، تلاش برای نگهداری را افزایش می‌دهند.
  • خطر استفاده بیش از حد برای تغییرات جزئی، منجر به پیچیدگی غیرضروری می‌شود.
  • مدیریت بسیاری از رابط‌ها ممکن است به آداپتورهای متعدد نیاز داشته باشد و طراحی را پیچیده کند.

کاربردهای الگوی طراحی آداپتور

ما می‌توانیم از الگوی طراحی آداپتور زمانی استفاده کنیم که:

  • ارتباط بین سیستم‌های ناسازگار را ممکن می‌سازد.
  • استفاده مجدد از کد یا کتابخانه‌های موجود بدون بازنویسی.
  • ساده ‌سازی ادغام اجزای جدید، حفظ انعطاف‌پذیری سیستم.
  • متمرکزسازی تغییرات سازگاری، تعمیر و نگهداری را آسان‌تر و ایمن‌تر می‌کند.

چه زمانی از الگوی طراحی آداپتور استفاده نکنیم؟

در موارد زیر از الگوی طراحی آداپتور استفاده نکنید:

  • اگر سیستم ساده باشد و همه اجزا سازگار باشند، ممکن است نیازی به آداپتور نباشد.
  • آداپتورها می ‌توانند سربار کمی ایجاد کنند که ممکن است در محیط‌های حساس به عملکرد نگران‌ کننده باشد.
  • وقتی مشکلی در سازگاری رابط وجود ندارد، استفاده از آداپتور می‌تواند اضافی باشد.
  • برای پروژه‌هایی با طول عمر بسیار کوتاه، سربار پیاده‌سازی آداپتور ممکن است ارزشش را نداشته باشد.

اجزای الگوی طراحی آداپتور

در زیر اجزای الگوی طراحی آداپتور آمده است:

رابط هدف: رابطی که کلاینت انتظار دارد و عملیاتی را که می‌تواند استفاده کند تعریف می‌کند.

آداپتیو: کلاس موجود با رابط ناسازگار که نیاز به ادغام دارد.

آداپتور: رابط هدف را پیاده ‌سازی می ‌کند و از آداپتور به صورت داخلی استفاده می‌کند و به عنوان یک پل عمل می‌کند.

کلاینت: از رابط هدف استفاده می‌ کند، بدون اینکه از جزئیات آداپتور یا آداپتور مطلع باشد.

پیاده‌سازی‌های مختلف الگوی طراحی آداپتور

الگوی طراحی آداپتور می‌تواند بسته به زبان برنامه‌نویسی و زمینه خاص، به روش‌های مختلفی اعمال شود. در ادامه پیاده‌سازی‌های اصلی آمده است:

۱. کلاس آداپتور (مبتنی بر وراثت)

در این رویکرد، کلاس آداپتور (وفق دهنده) از هر دو رابط هدف (که کلاینت انتظار دارد) و وفق‌پذیر (کلاس موجودی که نیاز به تطبیق دارد) ارث می‌برد.

زبان‌های برنامه‌نویسی که امکان وراثت چندگانه را فراهم می‌کنند، مانند C++، بیشتر از این تکنیک استفاده می‌کنند.

با این حال، در زبان‌هایی مانند جاوا و #C که از وراثت چندگانه پشتیبانی نمی‌کنند، این رویکرد کمتر مورد استفاده قرار می‌گیرد.

۲. آداپتور شیء (مبتنی بر ترکیب)

آداپتور شیء به جای وراثت، از ترکیب استفاده می‌کند. در این پیاده‌سازی، آداپتور نمونه‌ای از آداپتور را نگه می‌دارد و رابط هدف را پیاده‌سازی می‌کند.

این رویکرد انعطاف‌پذیرتر است زیرا به یک آداپتور واحد اجازه می‌دهد تا با چندین آداپتور کار کند و نیازی به پیچیدگی‌های وراثت ندارد.

آداپتور شیء به طور گسترده در زبان‌هایی مانند جاوا و سی شارپ استفاده می‌شود.

۳. آداپتور دوطرفه

یک آداپتور دوطرفه می‌تواند هم به عنوان هدف و هم به عنوان تطبیق‌پذیر عمل کند، بسته به اینکه کدام رابط فراخوانی می‌شود.

این نوع آداپتور به ویژه زمانی مفید است که دو سیستم نیاز به همکاری با یکدیگر داشته باشند و نیاز به تطبیق متقابل داشته باشند.

۴. آداپتور رابط (آداپتور پیش‌فرض)

هنگامی که فقط چند متد از یک رابط لازم است، می‌توان از یک آداپتور رابط استفاده کرد.

این امر به ویژه در مواردی مفید است که رابط شامل متدهای زیادی باشد و آداپتور پیاده‌سازی‌های پیش‌فرض را برای آنهایی که مورد نیاز نیستند فراهم می‌کند.

این رویکرد اغلب در زبان‌هایی مانند جاوا دیده می‌شود، جایی که کلاس‌های انتزاعی یا پیاده‌سازی‌های متد پیش‌فرض در رابط‌ها، فرآیند پیاده‌سازی را ساده می‌کنند.

الگوی طراحی آداپتور چگونه کار می‌کند؟

در زیر نحوه کار الگوی طراحی آداپتور آمده است:

مرحله ۱: کلاینت با فراخوانی یک متد روی آداپتور از طریق رابط هدف، درخواستی را آغاز می‌کند.

مرحله ۲: آداپتور درخواست کلاینت را به فرمتی که تطبیق‌پذیر، می‌تواند با استفاده از رابط تطبیق‌پذیر بفهمد، نگاشت یا تبدیل می‌کند.

مرحله ۳: تطبیق‌پذیر کار واقعی را بر اساس درخواست ترجمه شده از آداپتور انجام می‌دهد.

مرحله ۴: کلاینت نتایج فراخوانی را دریافت می‌کند، اما از وجود آداپتور یا جزئیات خاص آداپتورشونده بی‌اطلاع می‌ماند.

مثال:

بیایید سناریویی را در نظر بگیریم که در آن یک سیستم موجود داریم که از یک کلاس LegacyPrinter با متدی به نام ()printDocument استفاده می‌کند و می‌خواهیم آن را با یک سیستم جدید که انتظار یک رابط Printer با متدی به نام ()print را دارد، تطبیق دهیم. ما از الگوی طراحی Adapter برای سازگار کردن این دو رابط استفاده خواهیم کرد.

 

/* Adapter Design Pattern Example Code */
// Target Interface
public interface IPrinter
{
    void Print();
}
// Adaptee
public class LegacyPrinter
{
    public void PrintDocument()
    {
        System.Console.WriteLine("Legacy Printer is printing a document.");
    }
}
// Adapter
public class PrinterAdapter : IPrinter
{
    private LegacyPrinter legacyPrinter = new LegacyPrinter();
    public void Print()
    {
        legacyPrinter.PrintDocument();
    }
}
// Client Code
public class Client
{
    public static void ClientCode(IPrinter printer)
    {
        printer.Print();
    }
    public static void Main(string[] args)
    {
        // Using the Adapter
        PrinterAdapter adapter = new PrinterAdapter();
        ClientCode(adapter);
    }
}

منبع | نویسنده: