الگوهای طراحی، انواع مختلفی دارند که شامل رایجترین الگوها مانند پروتوتایپ، Factory و … میشوند. یکی دیگر از الگوهای شناختهشده طراحی نرمافزار، الگوی طراحی Adapter است. در این مقاله قصد داریم با مثال و قطعه کد، این الگو را بررسی کنیم.
الگوی طراحی آداپتور یا تبدیلکننده (Adapter) چیست؟
همانطور که از اسم این الگو مشخص است، زمانی که دو کلاس واسطهای غیرمرتبط و ناهمگون داشته باشند، این الگو واسط یکی را به دیگری تبدیل میکند که بتوانند با یکدیگر ارتباط برقرار کنند.
از این الگو که یک الگوی ساختاری است، زمانی استفاده میشود که در یک برنامه، بخواهیم دو کلاس غیرمرتبط با یکدیگر کار کنند. از این الگو در برنامههایی با کلاسهای آماده و یا از پیش نوشته شده، که طراحان نرمافزار اجازه تغییر در این کلاسها را نداده باشند، استفاده میشود. بسیاری از مثالهای الگوی Adapter درگیر ورودی و خروجی هستند؛ زیرا این دامنهای است که همیشه تغییر میکند.
این الگو مفهوم آداپتور را در دنیای واقعی شبیهسازی کردهاست. در واقعیت زمانی که دو موجودیت ناسازگار داریم که نمیتوانند به صورت مستقیم با یکدیگر کار کنند، یک آداپتور یا تبدیلکننده بین آنها قرار میدهیم که این دو موجودیت باهم سازگار شوند.
در واقع پلی بین دو موجودیت ناسازگار ایجاد میکنیم که آن دو موجودیت بتوانند باهم کار کنند. به عنوان مثال ما نمیتوانیم لپ تاپ یا دوربین عکاسی را مستقیما به برق وصل کنیم و از یک تبدیل استفاده می کنیم. الگوی adapter دقیقا از دنیای واقعی مفهوم گرفتهاست.
هدف این الگوی طراحی، تبدیل واسط موجود (جدید) به واسط مورد انتظار سیستم است و زمانی کاربرد دارد که واسط موجود قابل تغییر نباشد و امکان تغییر کد نیز وجود نداشته باشد. با کمک این الگو، امکان همکاری بین اشیایی که قبلا بخاطر داشتن رابط ناسازگار نمیتوانستند با هم کار کنند، فراهم میشود.
مثالی از استفاده Adapter Design Pattern
برای مثال، برنامههایی که در دهه ۸۰ میلادی نوشته شدهاند دارای UI بسیار ضعیفتری نسبت به برنامههای نوشته شده در قرن ۲۰ و ۲۱ میلادی هستند. اگر بازنویسی برنامههای قبلی بهصرفه نباشد و بخواهیم این برنامهها با سختافزارهای جدید همخوانی و سازگاری داشته باشند، باید برنامهای جدید طراحی کنیم.
این برنامه باید نقش واسط برنامه قدیمی و سختافزارهای جدید باشد و بین این دو برنامه ارتباط برقرار کند. به برنامهای که بین این دو برنامه قرار میگیرد، Adapter میگوییم.
ضرورت استفاده از الگوی Adapter
شاید اینطور به نظر برسد که استفاده از Adapter ضروری نیست و میتوانیم با تغییر واسط یکی از کلاسها، برنامه را اجرا کنیم. اما دقت کنید؛ این کار زمانی ممکن است که بتوانیم به کد کلاسها دسترسی داشته باشیم و تغییر کلاسها باعث به وجود آمدن مشکل در برنامه نشود و پیچیدگی برنامه را افزایش ندهد. این کار در اکثر مواقع بهصرفه و عملی نیست.
الگوی Adapter الگویی است كه در دنیای واقعی نمونههای زیادی از آن وجود دارد و به همین خاطر درك این الگو زیاد مشكل نخواهد بود.
ویژگیها و اجزای الگوی Adapter
Class Diagram
در شکل زیر، کلاس دیاگرام مربوط به این الگو را میبینید و در ادامه هرکدام از اجزای الگو معرفی شدهاند.
بنابر گفته GoF هدف از الگوی Adapter عبارت است از:
تبدیل واسط یک کلاس به واسط دیگری که کاربر توقع دارد. Adapter امکان همکاری بین چند کلاس را که به علت ناهمگونی واسطههایشان به روش دیگری نمیتوانند باهم کار کنند، فراهم میکند.
شکل ۱: کلاس دیاگرام الگوی Adapter
نقش کلاسها
- Target:
ارائه واسط که کلاینت از آن استفاده میکند.
- Adapter:
وفق دادن Interface Adaptee به Target Interface
- Adaptee:
کلاسی با واسط ناهماهنگ که میخواهیم در برنامه از آن استفاده کنیم.
- Client:
ارتباط با شی Target Interface
سایر ویژگیهای کلیدی الگوی طراحی Adapter
در جدول زیر به صورت خلاصه هدف الگو، نام یا نامهای دیگری که الگو با آن شناخته میشود، مواقعی که میتوان از این الگو استفاده کرد، نتایج استفاده از این الگو و الگوهای مرتبط به این الگو توضیح داده شده است.
جدول ۱: ویژگیهای Adapter
عنوان |
شرح |
نام |
Adapter Pattern |
هدف |
واسط یک کلاس را به واسط دیگری که مورد انتظار یک مشتری است تبدیل میکند. این الگو امکان همکاری بین اشیایی را که قبلا بهخاطر داشتن واسطهای ناسازگار نمیتوانستند با هم کار کنند، فراهم میکند. |
نامهای دیگر |
این الگو با نام Wrapper نیز شناخته می شوند. |
کاربرد |
زمانی از الگوی Adapter استفاده کنید که :
|
نتایج |
|
الگوهای مرتبط |
Factory Method: در تطبیقدهندههای شی بهتر است نمونهسازی از Adaptee را به طور مستقیم انجام ندهید و به جای آن تکنیکی مانند Factory Method را به کار ببرید. |
یک مثال برای الگوی طراحی Adapter
در زیر مثالی از این الگو پیادهسازی شدهاست.
مساله: در یک آزمایشگاه شیمی قصد داریم که ویژگیهایی مانند ساختار، وزن، نقطه جوش و انجماد مربوط به آب، بنزن و الکل را به دانشجویان این رشته نشان دهیم. (قابل ذکر است، از قبل سرویسی برای بیان ویژگیهای مواد تعریف شده که با واسط برنامه ما همخوانی ندارد و ما باید ارتباط بین این دو را برقرار کنیم).
راهحل: به دلیل تفاوت در واسطی که از قبل برای ویژگیهای مواد پیادهسازی شده است، با واسط مربوط به نمایش برای کاربر، از الگوی Adapter استفاده میکنیم.
Compound: طبق کلاس دیاگرام الگو نقش کلاس Target را دارد، که در آن متغیرهایی برای نام و ویژگیهای مواد تعریف شده و دارای یک متد Virtual به نام Display است که در آن نام ماده مربوطه را نمایش میدهد.
ChemicalDatabank: نقش کلاس Adaptee را دارد. کلاسی با واسط ناهماهنگ، که میخواهیم در برنامه خود از آن استفاده کنیم. این کلاس دارای سه متد است که نقطه جوش، انجماد، ساختار و وزن مواد را برمیگردانند.
RichCompound: نقش کلاس Adapter را دارد که کلاس Compound را به ارث میبرد و در متد Display، یک نمونه از کلاس ChemicalDatabank ایجاد کرده و متدهای آن را فراخوانی میکند.
پیادهسازی:
namespace Chemical_Adapter { class Program { static void Main(string[] args) { Compound water = new RichCompound("Water"); water.Display(); Compound benzene = new RichCompound("Benzene"); benzene.Display(); Compound ethanol = new RichCompound("Ethanol"); ethanol.Display(); Console.ReadKey(); } } } class Compound { protected string _chemical; protected float _boilingPoint; protected float _meltingPoint; protected double _molecularWeight; protected string _molecularFormula; public Compound(string chemical) { this._chemical = chemical; } public virtual void Display() { Console.WriteLine("\nCompound: {0} ------ ", _chemical); } } class RichCompound : Compound { private ChemicalDatabank _bank; public RichCompound(string name) : base(name) { } public override void Display() // The Adaptee _bank = new ChemicalDatabank(); _boilingPoint = _bank.GetCriticalPoint(_chemical, "B"); _meltingPoint = _bank.GetCriticalPoint(_chemical, "M"); _molecularWeight = _bank.GetMolecularWeight(_chemical); _molecularFormula = _bank.GetMolecularStructure(_chemical); base.Display(); Console.WriteLine(" Formula: {0}", _molecularFormula); Console.WriteLine(" Weight : {0}", _molecularWeight); Console.WriteLine(" Melting Pt: {0}", _meltingPoint); Console.WriteLine(" Boiling Pt: {0}", _boilingPoint); } } class ChemicalDatabank { public float GetCriticalPoint(string compound, string point) { // Melting Point if (point == "M") { switch (compound.ToLower()) { case "water": return 0.0f; case "benzene": return 5.5f; case "ethanol": return -114.1f; default: return 0f; } } // Boiling Point Else { switch (compound.ToLower()) { case "water": return 100.0f; case "benzene": return 80.1f; case "ethanol": return 78.3f; default: return 0f; } } } public string GetMolecularStructure(string compound) { switch (compound.ToLower()) { case "water": return "H20"; case "benzene": return "C6H6"; case "ethanol": return "C2H5OH"; default: return ""; } } public double GetMolecularWeight(string compound) { switch (compound.ToLower()) { case "water": return 18.015; case "benzene": return 78.1134; case "ethanol": return 46.0688; default: return 0d; } } }
خروجی :
جمعبندی
همانطور که در این مقاله گفتیم، الگوی آداپتور تا حد زیادی به کاربرد آداپتور در دنیای واقعی شباهت دارد. در واقع در الگوی طراحی Adapter، ما به دنبال نوشتن یک ساختار و برنامه واسط، برای ایجاد ارتباط بین دو ساختار و رابط ناهمگون هستیم.
به طور معمول از این الگوی طراحی زمانی استفاده میکنیم که امکان ایجاد تغییر در برنامهها و واسطهای فعلی را نداریم.
منابع:
دیدگاهتان را بنویسید