زمان تخمینی مطالعه: 0 دقیقه
الگوی Adapter یک الگوی ساختاری(Structural) است که به اینترفیس یک کلاس موجود اجازه میدهد تا به عنوان اینترفیس کلاس دیگر استفاده شود. در واقع این الگوها باعث به وجود آمدن پلی بین دو اینترفیس ناسازگار میشوند. الگویAdapter که با نام Wrapper(پوشاننده) نیز شناخته میشود به اشیا با رابطهای ناسازگار اجازه همکاری میدهد. این الگو شامل یک کلاس واحد است که به عنوان آداپتور شناخته میشود، که مسئول پیوستن به عملکردهای اینترفیسهای مستقل یا ناسازگار است.
بیان مسئله: تصور کنید که در حال ایجاد یک برنامه نظارت بر بازار سهام هستید. این برنامه دادههای سهام را از چندین منبع در قالب XML دانلود میکند و سپس گرافها و نمودارهای زیبا را برای کاربر نمایش میدهد. در برخی مواقع، تصمیم میگیرید با ادغام یک کتابخانه تحلیلی مجزا، برنامه را بهبود ببخشید. اما نکتهای که وجود دارد این است که کتابخانه استفاده شده برای تجزیه و تحلیل فقط با دادهها موجود که در قالب JSON هستند کار میکند.
در برنامه خود میتوانید کتابخانه مورد استفاده را برای کار با XML تغییر دهید. با این حال، این موضوع ممکن است برخی از کدهای موجود را که به کتابخانه متکی هستند، خراب کند. و بدتر از آن، ممکن است در وهله اول به کد منبع کتابخانه دسترسی نداشته باشید، که این رویکرد را غیر ممکن میکند. پس چه باید کرد؟؟؟
راه حل این موضوع این است که میتوانید یک آداپتور(Adapter) ایجاد کنید. این مفهوم یک شی خاص است که اینترفیس یک شی را برای تطابق با اشیاء دیگر تغییر میدهد. یک آداپتور یکی از اشیاء را میپوشاند تا پیچیدگی تبدیل در پشت صحنه را پنهان کند. شی پوشیده شده حتی از وجود آداپتور آگاه نیست. برای مثال، میتوانید شیای را که بر حسب متر و کیلومتر کار میکند، با آداپتوری بپوشانید که تمام دادهها را به واحدهای امپریال مانند فوت و مایل تبدیل کند.
آداپتورها نه تنها میتوانند دادهها را به فرمتهای مختلف تبدیل کنند، بلکه میتوانند به اشیاء با اینترفیسهای مختلف کمک کنند. در اینجا نحوه عملکرد آنها آمده است:
- آداپتور یک اینترفیس، سازگار با یکی از اشیاء موجود را دریافت میکند.
- با استفاده از این اینترفیس، شی موجود میتواند با خیال راحت متدهای الگوی Adapter را فراخوانی کند.
- پس از دریافت فراخوانی، آداپتور درخواست را به شی دوم ارسال میکند، اما در قالب و ترتیبی که شی دوم با آن سازگار است و انتظار دارد.
گاهی اوقات حتی میتوان آداپتوری دو طرفه ایجاد کرد که بتواند فراخوانیها را در هر دو جهت تبدیل کند.
اجازه دهید مجددا به برنامه بازار سهام خود برگردیم. برای حل معضل فرمتهای ناسازگار، میتوانید آداپتورهای XML-to-JSON را برای هر کلاسی از کتابخانه تحلیلی که کد شما مستقیماً با آن کار میکند ایجاد کنید. سپس کد خود را طوری تنظیم میکنید که فقط از طریق این آداپتورها با کتابخانه ارتباط برقرار کند. هنگامی که یک آداپتور فراخوانی را دریافت میکند، دادههای XML ورودی را به یک ساختار JSON ترجمه میکند و فراخوانی را به روشهای مناسب یک شی قابل تحلیل پیچیده ارسال میکند.
نمونه قابل قیاس در دنیای واقعی
هنگامی که برای اولین بار از وطن خود به کشوری دیگر سفر میکنید، ممکن است هنگام تلاش برای شارژ لپ تاپ یا موبایل خود غافلگیر شوید. زیرا استانداردهای دوشاخه و پریز برق در کشورهای مختلف متفاوت است. به همین دلیل است که مثلا دوشاخه کشور شما با پریز کشور دیگر مناسب نیست.
برای حل مشکل میتوان در هنگام سفر به خارج از کشور خود از آپتوری استفاده کنید که دارای انواع تبدیلهای پریز را داشته باشد تا دیگر دچار مشکل در شارژ لپ تاپ یا موبایل خود نشوید.
ساختار الگوی Adapter
در این بخش به بررسی ساختار الگوی آداپتور میپردازیم و پیادهسازی آن را به صورت UML خواهیم دید. در حالت کلی الگوی Adapter دارای دو مدل ساختار متفاوت است که در ادامه هر دو مدل را بررسی خواهیم کرد.
– آداپتور شی(Object Adapter)
این پیاده سازی از اصل ترکیب شی استفاده میکند: آداپتور اینترفیس یک شی را پیادهسازی کرده و شی دیگر را میپوشاند. این مدل را میتوان در تمام زبانهای برنامهنویسی رایج پیادهسازی کرد.
- گام 1: در اینجا Client کلاسی است که شامل منطق تجاری موجود برنامه است.
- گام 2: اینترفیس Client پروتکلی را توصیف میکند که سایر کلاسها باید از آن پیروی کنند تا بتوانند با کد Client همکاری کنند.
- گام 3: کلاس Service یک کلاس مفید است. کلاینت نمیتواند مستقیماً از این کلاس استفاده کند زیرا دارای یک اینترفیس ناسازگار است.
- گام 4: در اینجا Adapter کلاسی است که میتواند هم با کلاینت و هم با خود سرویس کار کند: اینترفیس مشتری را پیادهسازی میکند، در حالی که شی سرویس را میپوشاند. آداپتور فراخوانیها را از طریق اینترفیس کلاینت دریافت میکند و آنها را به فراخوانیهایی با شیء سرویس پوشیده شده در قالبی که قابل درک است ترجمه میکند.
- گام 5: کد کلاینت تا زمانی که از طریق اینترفیس کلاینت با آداپتور کار میکند با کلاس concrete آداپتور همراه نمیشود. به لطف این موضوع، میتوانید انواع جدیدی از آداپتورها را بدون شکستن کد کلاینت موجود وارد برنامه کنید. این میتواند زمانی مفید باشد که اینترفیس کلاس سرویس تغییر کند یا جایگزین شود: شما فقط میتوانید یک کلاس آداپتور جدید بدون تغییر کد کلاینت ایجاد کنید.
– آداپتور کلاس(Class Adapter)
این پیادهسازی از وراثت استفاده میکند: آداپتور اینترفیسها را از هر دو شی به طور همزمان به ارث میبرد. توجه داشته باشید که این رویکرد فقط در زبانهای برنامهنویسی که از وراثت چندگانه پشتیبانی میکنند، مانند ++C قابل پیادهسازی است.
آداپتور کلاس(Class Adapter) نیازی به پوشش دادن هیچ شیئی ندارد زیرا رفتارها را هم از کلاینت و هم از سرویس به ارث میبرد. معمولا انطباق(Adaptation) در روشهای نادیده گرفته شده(Overridden) اتفاق میافتد. آداپتور حاصل میتواند به جای یک کلاس کلاینت موجود استفاده شود.
شبه کد(Pseudocode)
این مثال از الگوی Adapter مبتنی بر درگیری کلاسیک موجود مابین میخهای مربعی(square peg) و سوراخهای گرد است.
آداپتور وانمود میکند که یک میخ گرد است که شعاع آن برابر با نیمی از قطر مربع است (به عبارت دیگر، شعاع کوچکترین دایرهای که میتواند میخ مربعی را در خود جای دهد).
// 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
قابلیتها و کاربردها
- از کلاس Adapter زمانی استفاده کنید که میخواهید از کلاسی موجود استفاده کنید اما در این هنگام اینترفیس آن با بقیه کد شما ناسازگار باشد. در این حالت الگوی Adapter به شما امکان میدهد یک کلاس لایه میانی ایجاد کنید که به عنوان مترجم بین کد شما و یک کلاس قدیمی، یک کلاس شخص ثالث یا هر کلاس دیگری با اینترفیس کاربری عجیب و غریب عمل میکند.
- هنگامی که میخواهید چندین زیر کلاس موجود را که فاقد برخی عملکردهای مشترک هستند و نمیتوان آنها را به سوپرکلاس اضافه کرد، مجدداً استفاده کنید، از الگوی Adapter استفاده کنید. شما میتوانید هر زیر کلاس را گسترش داده و عملکرد فراموش شده را در کلاسهای فرزند جدید قرار دهید. با این حال، باید کد را در تمام این کلاسهای جدید کپی کنید، که به نظر کار درستی نیست.
راه حل بسیار بهتر قرار دادن قابلیتهای از دست رفته در یک کلاس آداپتور است. سپس اشیاء با ویژگیهای از دست رفته را در داخل آداپتور قرار میدهید و ویژگیهای مورد نیاز را به صورت پویا به دست میآورید. برای این کار، کلاسهای هدف باید یک اینترفیس مشترک داشته باشند و فیلد آداپتور باید از آن اینترفیس پیروی کند. این رویکرد بسیار شبیه به الگوی Decorator است.
نحوه پیادهسازی
- مطمئن شوید که حداقل دو کلاس با اینترفیسهای ناسازگار دارید:
- یک کلاس سرویس مفید که نمیتوانید آن را تغییر دهید (اغلب کلاسهای شخص ثالث، قدیمی یا با وابستگیهای موجود زیاد).
- یک یا چند کلاس مشتری که بوسیله استفاده از کلاس سرویس سود میبرند.
- اینترفیس مشتری را اعلان کنید و نحوه ارتباط مشتریان با سرویس را شرح دهید.
- کلاس آداپتور را ایجاد کنید و کاری کنید که آن از اینترفیس مشتری پیروی کند. فعلا تمام متدها را خالی بگذارید.
- یک فیلد به کلاس آداپتور اضافه کنید تا یک رفرنس در شیء سرویس ذخیره شود. روش معمول این است که این فیلد را از طریق سازنده مقداردهی اولیه کنید، اما گاهی اوقات هنگام فراخوانی متدهای آن، انتقال آن به آداپتور راحتتر است.
- یکی یکی تمام متدهای اینترفیس کلاینت را در کلاس آداپتور پیادهسازی کنید. آداپتور باید بیشتر کار واقعی را به شیء سرویس واگذار کند و فقط اینترفیس یا تبدیل فرمت داده را مدیریت کند.
- مشتریان باید از آداپتور بوسیله اینترفیس مشتری استفاده کنند. این موضوع به شما امکان میدهد آداپتورها را بدون تأثیر بر روی کد مشتری تغییر دهید یا گسترش دهید.
مزایا و معایب الگوی Adapter
این الگوی طراحی دارای مزایا و معایبی به شرح زیر است:
– مزایا
- اصل مسئولیت واحد: میتوانید اینترفیس یا کد تبدیل داده را از منطق تجاری اصلی برنامه جدا کنید.
- اصل باز/بسته: شما میتوانید انواع جدیدی از آداپتورها را بدون شکستن کد مشتری موجود به برنامه معرفی کنید، به شرطی که از طریق اینترفیس مشتری با آداپتورها کار کنند.
– معایب
- استفاده از الگوی Adapter باعث میشود که پیچیدگی کلی کد افزایش یابد زیرا باید مجموعهای از اینترفیسها و کلاسهای جدید را معرفی کنید. گاهی اوقات سادهتر است که کلاس سرویس را طوری تغییر دهید که با بقیه کد شما مطابقت داشته باشد.
ارتباط با الگوهای دیگر
- الگوی Bridge معمولاً از قبل طراحی میشود و به شما امکان میدهد بخشهایی از یک برنامه را مستقل از یکدیگر توسعه دهید. از سوی دیگر، الگوی Adapter معمولاً با یک برنامه موجود استفاده میشود تا برخی از کلاسهای ناسازگار بتوانند به خوبی با هم کار کنند.
- آداپتور یک رابط کاملا متفاوت برای دسترسی به یک شی موجود فراهم میکند. از طرف دیگر، با الگوی Decorator اینترفیس ثابت بوده و یا گسترش مییابد. علاوه بر این، Decorator از ترکیب بازگشتی پشتیبانی میکند، که با استفاده از آداپتور امکان پذیر نیست.
- با آداپتور شما از طریق اینترفیسهای مختلف به یک شی موجود دسترسی پیدا میکنید. با الگوی Proxy، اینترفیس ثابت میماند. با Decorator شما از طریق یک اینترفیس پیشرفته به شی دسترسی دارید.
- الگوی Facade یک اینترفیس جدید برای اشیاء موجود تعریف میکند، در حالی که الگوی Adapter سعی میکند رابط موجود را قابل استفاده کند. آداپتور معمولاً فقط یک شی را میپوشاند، در حالی که Facade با یک زیر سیستم کامل از اشیا کار میکند.
- الگوهای Bridge، الگوی State و الگوی Strategy (و تا حدی آداپتور) ساختارهای بسیار مشابهی دارند. در واقع، همه این الگوها بر اساس ترکیببندی هستند که کار را به اشیاء دیگر واگذار میکند. با این حال، همه آنها مشکلات مختلفی را حل میکنند. یک الگو فقط دستور العملی برای ساختار کد شما به روشی خاص نیست. همچنین میتواند مشکلی را که الگو حل میکند با توسعهدهندگان دیگر در میان بگذارد.
2 پاسخ
بسیار عالی و ساده یک مفهوم پیچیده ای مثل این رو توضیح دادید و از مراجع سخت انگلیسی اون رو برای ما قابل دسترسی کردید. سپاس
با سلام و عرض ادب خدمت شما بزرگوار جناب مهندس نادری عزیز از اساتید برنامه نویسی بسیار با دانش و مطلع. خوشحالم که مطلب مورد توجه شما بوده است.