Skip to content

Adapter

amirhfayyazian edited this page Aug 22, 2022 · 6 revisions

الگوی طراحی Adapter

الگوی طراحی Adapter یک الگوی ساختاری(structural) است که به اشیاء با رابط های ناسازگار امکان همکاری می دهد. یک آداپتور به دو رابط ناسازگار اجازه می‌دهد تا بتوانند با هم کار کنند. این یک تعریف کلی از مفهوم آداپتور است. ممکن است رابط ها ناسازگار باشند ولی قابلیت درونی آنها باید سازگار با نیاز باشد. الگوی طراحی آداپتور از طریق تبدیل رابط یک کلاس به رابط مورد انتظار توسط کلاینت، به کلاس‌های ناسازگار اجازه می‌دهد تا بتوانند از قابلیت‌های همدیگر استفاده کنند. این الگو، یک شئ غیر سازگار را در یک adapter برای سازگاری با سایر کلاس‌ها پنهان می‌کند. adapter این اجازه را می‌دهد که دو یا چند شئ ناسازگار با هم ارتباط یا تعامل برقرار کنند.

معمولاً با این هدف مورد استفاده قرار می گیرد که بدون تغییر در کد اصلی، بتوان استفاده از کلاس های فعلی را مقدور ساخت.


در دنیای واقعی

به عنوان یک مثال در دنیای حقیقی، به شارژر گوشی خود نگاه کنید. بر روی آن نوشته که ورودی ۲۲۰ ولت متناوب و خروجی ۵ یا ۹ ولت مستقیم. شما هیچگاه نمیتوانید گوشی خود را مستقیما و بدون یک واسط(شارژر) به پریز برق متصل کنید؛ زیرا در این‌صورت، شاهد نابودی گوشی خود خواهید بود. گوشی شما ولتاژی در حدود ۵ ولت(یا ۹ ولت) را می‌تواند تحمل کند. اما ولتاژی در حدود ۲۲۰ ولت قطعا منجر به نابودی گوشی موبایلتان خواهد شد.

یا مثلا سوکت های برق 3 شاخه رو حتما دیدین . این سوکت ها ممکنه در انگلستان به خوبی کار کنند ولی وقتی به ایران آورده می شود دیگر قابل استفاده نیست زیرا اکثر پریزهای برق داخل منازل از نوع 2 شاخه هستند پس باید یه آداپتور تهیه کنید تا این 3 شاخه را به 2 شاخه تبدیل کند و بتوان از آن استفاده کنید .


مسئله

تصور کنید که شما در حال ایجاد یک برنامه نظارت بر بازار سهام هستید. برنامه داده های سهام را از چندین منبع با فرمت XML بارگیری می کند و سپس نمودارها و دیاگرام‌های زیبا را برای کاربر نمایش می دهد. در بعضی از مواقع ، تصمیم می گیرید با ادغام یک کتابخانه تحلیلی هوشمند شخص ثالث ، برنامه را بهبود بخشید. اما یک واقعیت وجود دارد: کتابخانه تحلیلی فقط با داده ها با فرمت JSON کار می کند. برای این‌که کتابخانه تحلیل‌گر بتواند با داده‌های XML کار کند، می توانید کتابخانه را به نحوی تغییر دهید که داده‌های XML را به عنوان ورودی بپذیرد. با این وجود، این کار ممکن است برخی از کدهای موجود را که به کتابخانه متکی هستند دچار مشکل کند. و بدتر اینکه، ممکن است در وهله اول به کد منبع کتابخانه(Source Code) دسترسی نداشته باشید که در این صورت، تغییر دادن کتابخانه، روشی غیرممکن خواهد شد.

راه حل

برای حل معضل قالب های ناسازگار، می توانید آداپتورهای XML-to-JSON برای هر کلاس از کتابخانه تحلیلی که کد شما مستقیماً با آن کار می کند، ایجاد کنید. سپس کد خود را به گونه‌ای تنظیم کنید که فقط از طریق این Adapter ها با کتابخانه ارتباط برقرار کند. هنگامی که Adapter یک فراخوانی دریافت می کند، داده های XML ورودی را به یک ساختار JSON ترجمه می کند و سپس این فراخوانی را به روشی مناسب به کتابخانه تحلیلگر منتقل می‌کند.

ساختار الگوی طراحی Adapter

2 نوع آداپتور (Adapter Design Pattern) معرفی کردن :

  1. inheritance version که به عنوان class version هم شناخته می شود.

در این روش کلاس Adapter از ارث بری چند گانه استفاده می‌کند و Interface مرتبط به Adaptee را به Interface مرتبط به Target سازگار می‌نماید. برای درک تعریف بالا مثالی را بررسی می‌کنیم، در ابتدا شکل زیر را مشاهده نمایید:

در شکل ملاحظه می‌کنید، متد SpecificationRequet واقع در Adaptee می‌تواند نیاز Client را برطرف نماید، اما Client،چیزی را که مشاهده می‌کند اینترفیس Itarget می‌باشد، به عبارتی Client بطور مستقیم نمی‌تواند با Adaptee ارتباط برقرار کند، بنابراین اگر بخواهیم از طریق Itarget نیاز Client را برطرف نماییم، لازم است کلاسی بین Itarget و Adaptee به جهت تبادل اطلاعات ایجاد کنیم، که Adapter نامیده می‌شود. حال در روش Class Adapter، کلاس Adapter جهت تبادل اطلاعات بین ITarget و Adaptee هر دو را در خود Implement می‌نماید، به عبارتی از هر دو مشتق (Inherit) می‌شود. در ادامه شکل بالا را بصورت کد پیاده سازی می‌نماییم.

class Adaptee
{
  public void SpecificationRequest()
  {
    Console.WriteLine("SpecificationRequest() is called");
  }
}

interface ITarget
{
  void Request();

}

class Adapter:Adaptee, ITarget
{
  public void Request()
  {
    SpecificationRequest();
  }
}

class MainApp
{
  static void Main()
  {
    ITarget target = new Adapter();
    target.Request();

    Console.ReadKey();
  }
}


  1. composition version که به عنوان object version هم شناخته می شود. می دانیم در زبان برنامه نویسی #C هر کلاس فقط می‌تواند از یک کلاس دیگر Inherit شود، به طوری که هر کلاس نمی‌تواند بیش از یک کلاس Parent داشته باشد، بنابراین اگر Client شما بخواهد از امکانات و قابلیت‌های چندین کلاس Adaptee استفاده نماید، روش Class Adapter نمی‌تواند پاسخگوی نیازتان باشد، بلکه می‌بایست از روش Object Adapter استفاده نمایید. شکل زیر بیانگر روش Object Adapter می‌باشد:

همانطور که در شکل ملاحظه می‌کنید، در این روش کلاس Adapter به جای Inherit نمودن از کلاس Adaptee، آبجکتی از کلاس Adaptee را در خود ایجاد می‌نماید، بنابراین با این روش شما می‌توانید به چندین Adaptee از طریق کلاس Adapter دسترسی داشته باشید. پیاده سازی کدی شکل بالا به شرح ذیل می‌باشد:

class Adaptee
{
  public void SpecificRequest()
  {
    MessageBox.Show("Called SpecificRequest()");
  }
}

interface ITarget
{
  void Request();
}

class Adapter: ITarget
{
  private Adaptee _adptee = new Adaptee();

  public void Request()
  {
    _adptee.SpecificationRequest();
  }
}

class MainApp
{
  static void Main()
  {
    ITarget target = new Adapter();
    target.Request();

    Console.ReadKey();
  }
}

برای درک تفاوت Class Adapter و Object Adapter ، پیاده سازی کلاس Adapter را مشاهده نمایید، که در کد بالا به جای Inherit نمودن از کلاس Adaptee ، آبجکت آن را ایجاد نمودیم. واضح است که Object Adapter انعطاف پذیرتر نسبت به Class Adapter می‌باشد.

مزایا و معایب

  1. با استفاده از این الگو می‌توانیم فرآیند سازگار کردن کتابخانه خارجی را به قسمتی دیگر منتقل کنیم (اصل اول SOLID)
  2. بدون اینکه قسمت کلاینت را تغییر دهیم می‌توانیم با بی‌نهایت سرویس کار بکنیم (اصل دوم SOLID)
  3. وابستگی قسمت کلاینت به کتابخانه‌های خارجی حذف میشود (اصل پنجم SOLID)
  4. گاهی می‌توانیم با تغییر دادن کدهای کتابخانه خارجی به هدفمان برسیم و اضافه کردن این الگو ممکن است باعث پیچیدگی بیش از حد برنامه شود.

مثال

// Say you have two classes with compatible interfaces:
// RoundHole and RoundPeg.
class RoundHole is
    constructor RoundHole(radius) { ... }

    method getRadius() is
        // Return the radius of the hole.

    method fits(peg: RoundPeg) is
return this.getRadius() >= peg.getRadius()

class RoundPeg is
    constructor RoundPeg(radius) { ... }

    method getRadius() is
// Return the radius of the peg.


// But there's an incompatible class: SquarePeg.
class SquarePeg is
    constructor SquarePeg(width) { ... }

    method getWidth() is
// Return the square peg width.


// An adapter class lets you fit square pegs into round holes.
// It extends the RoundPeg class to let the adapter objects act
// as round pegs.
class SquarePegAdapter extends RoundPeg is
    // In reality, the adapter contains an instance of the
    // SquarePeg class.
    private field peg: SquarePeg

    constructor SquarePegAdapter(peg: SquarePeg) is
        this.peg = peg

    method getRadius() is
        // The adapter pretends that it's a round peg with a
        // radius that could fit the square peg that the adapter
        // actually wraps.
        return peg.getWidth() * Math.sqrt(2) / 2


// Somewhere in client code.
hole = new RoundHole(5)
rpeg = new RoundPeg(5)
hole.fits(rpeg) // true

small_sqpeg = new SquarePeg(5)
large_sqpeg = new SquarePeg(10)
hole.fits(small_sqpeg) // this won't compile (incompatible types)

small_sqpeg_adapter = new SquarePegAdapter(small_sqpeg)
large_sqpeg_adapter = new SquarePegAdapter(large_sqpeg)
hole.fits(small_sqpeg_adapter) // true
hole.fits(large_sqpeg_adapter) // false

مثال Python

class Target:
    """
    The Target defines the domain-specific interface used by the client code.
    """

    def request(self) -> str:
        return "Target: The default target's behavior."

class Adaptee:
    """
    The Adaptee contains some useful behavior, but its interface is incompatible
    with the existing client code. The Adaptee needs some adaptation before the
    client code can use it.
    """

    def specific_request(self) -> str:
        return ".eetpadA eht fo roivaheb laicepS"


class Adapter(Target, Adaptee):
    """
    The Adapter makes the Adaptee's interface compatible with the Target's
    interface via multiple inheritance.
    """

    def request(self) -> str:
        return f"Adapter: (TRANSLATED) {self.specific_request()[::-1]}"


def client_code(target: "Target") -> None:
    """
    The client code supports all classes that follow the Target interface.
    """

    print(target.request(), end="")


if __name__ == "__main__":
    print("Client: I can work just fine with the Target objects:")
    target = Target()
    client_code(target)
    print("\n")

    adaptee = Adaptee()
    print("Client: The Adaptee class has a weird interface. "
          "See, I don't understand it:")
    print(f"Adaptee: {adaptee.specific_request()}", end="\n\n")

    print("Client: But I can work with it via the Adapter:")
    adapter = Adapter()
    client_code(adapter)

مثال TypeScript

/**
 * The Target defines the domain-specific interface used by the client code.
 */
class Target {
    public request(): string {
        return 'Target: The default target\'s behavior.';
    }
}

/**
 * The Adaptee contains some useful behavior, but its interface is incompatible
 * with the existing client code. The Adaptee needs some adaptation before the
 * client code can use it.
 */
class Adaptee {
    public specificRequest(): string {
        return '.eetpadA eht fo roivaheb laicepS';
    }
}

/**
 * The Adapter makes the Adaptee's interface compatible with the Target's
 * interface.
 */
class Adapter extends Target {
    private adaptee: Adaptee;

    constructor(adaptee: Adaptee) {
        super();
        this.adaptee = adaptee;
    }

    public request(): string {
        const result = this.adaptee.specificRequest().split('').reverse().join('');
        return `Adapter: (TRANSLATED) ${result}`;
    }
}

/**
 * The client code supports all classes that follow the Target interface.
 */
function clientCode(target: Target) {
    console.log(target.request());
}

console.log('Client: I can work just fine with the Target objects:');
const target = new Target();
clientCode(target);

console.log('');

const adaptee = new Adaptee();
console.log('Client: The Adaptee class has a weird interface. See, I don\'t understand it:');
console.log(`Adaptee: ${adaptee.specificRequest()}`);

console.log('');

console.log('Client: But I can work with it via the Adapter:');
const adapter = new Adapter(adaptee);
clientCode(adapter);

Is necessary

Design Pattern

Creational

Structural

Behavioral

Template

Clone this wiki locally