الگوی طراحی آداپتور Adapter چیست؟

دسته بندی: طراحی نرم‌افزار
5 دقیقه زمان مطالعه
1401/03/03
1 نظر

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

الگوی طراحی آداپتور یا تبدیل‌کننده (Adapter) چیست؟

همانطور که از اسم این الگو مشخص است، زمانی که دو کلاس واسط‌های غیرمرتبط و ناهمگون داشته باشند، این الگو واسط یکی را به دیگری تبدیل می‌کند که بتوانند با یکدیگر ارتباط برقرار کنند.

از این الگو که یک الگوی ساختاری است، زمانی استفاده می‌شود که در یک برنامه، بخواهیم دو کلاس غیرمرتبط با یکدیگر کار کنند. از این الگو در برنامه‌هایی با کلاس‌های آماده و یا از پیش نوشته شده، که طراحان نرم‌افزار اجازه تغییر در این کلاس‌ها را نداده باشند، استفاده می‌شود. بسیاری از مثال‌های الگوی Adapter درگیر ورودی و خروجی هستند؛ زیرا این دامنه‌ای است که همیشه تغییر می‌کند.

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

در واقع پلی بین دو موجودیت ناسازگار ایجاد می‌کنیم که آن دو موجودیت بتوانند باهم کار کنند. به عنوان مثال ما نمی‌توانیم لپ تاپ یا دوربین عکاسی را مستقیما به برق وصل کنیم و از یک تبدیل استفاده می کنیم. الگوی adapter دقیقا از دنیای واقعی مفهوم گرفته‌است.

هدف این الگوی طراحی، تبدیل واسط موجود (جدید) به واسط مورد انتظار سیستم است و زمانی کاربرد دارد که واسط موجود قابل تغییر نباشد و امکان تغییر کد نیز وجود نداشته باشد. با کمک این الگو، امکان همکاری بین اشیایی که قبلا بخاطر داشتن رابط ناسازگار نمی‌توانستند با هم کار کنند، فراهم می‌شود.

مثالی از استفاده Adapter Design Pattern

برای مثال، برنامه‌هایی که در دهه ۸۰ میلادی نوشته شده‌اند دارای UI بسیار ضعیف‌تری نسبت به برنامه‌های نوشته شده در قرن ۲۰ و ۲۱ میلادی هستند. اگر بازنویسی برنامه‌های قبلی به‌صرفه نباشد و بخواهیم این برنامه‌ها با سخت‌افزارهای جدید همخوانی و سازگاری داشته باشند، باید برنامه‌ای جدید طراحی کنیم.

این برنامه باید نقش واسط برنامه قدیمی و سخت‌افزارهای جدید باشد و بین این دو برنامه ارتباط برقرار کند. به برنامه‌ای که بین این دو برنامه قرار می‌گیرد، Adapter می‌گوییم.

ضرورت استفاده از الگوی Adapter

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

الگوی Adapter الگویی است كه در دنیای واقعی نمونه‌های زیادی از آن وجود دارد و به همین خاطر درك این الگو زیاد مشكل نخواهد بود.

ویژگی‌ها و اجزای الگوی Adapter

Class Diagram

در شکل زیر، کلاس دیاگرام مربوط به این الگو را می‌بینید و در ادامه هرکدام از اجزای الگو معرفی شده‌اند.

بنابر گفته GoF هدف از الگوی Adapter عبارت است از:

تبدیل واسط یک کلاس به واسط دیگری که کاربر توقع دارد. Adapter امکان همکاری بین چند کلاس را که به علت ناهمگونی واسطه‌هایشان به روش دیگری نمی‌توانند باهم کار کنند، فراهم می‌کند.

Class Diagram الگو طراحی adapter

شکل ۱: کلاس دیاگرام الگوی Adapter

نقش کلاس‌‌ها

  • Target:

ارائه واسط که کلاینت از آن استفاده می‌کند.

  • Adapter:

وفق دادن Interface Adaptee به Target Interface

  • Adaptee:

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

  • Client:

ارتباط با شی Target Interface

سایر ویژگی‌های کلیدی الگوی طراحی Adapter

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

جدول ۱: ویژگی‌های Adapter

عنوان

شرح

نام

Adapter Pattern

هدف

واسط یک کلاس را به واسط دیگری که مورد انتظار یک مشتری است تبدیل می‌کند. این الگو امکان همکاری بین اشیایی را که قبلا به‌خاطر داشتن واسط‌های ناسازگار نمی‌توانستند با هم کار کنند، فراهم می‌کند.

نام‌های دیگر

این الگو با نام Wrapper نیز شناخته می شوند.

کاربرد

زمانی از الگوی Adapter استفاده کنید که :

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

نتایج

  • این الگو امكان استفاده از كلاس‌های از پیش‌ساخته را در قالب كلاس جدید بدون تغییر در ساختار آن‌ها و بدون محدود کردن آن‌ها به واسط، فراهم می‌آورد.
  • الگوی 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

جمع‌بندی

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

به طور معمول از این الگوی طراحی زمانی استفاده می‌کنیم که امکان ایجاد تغییر در برنامه‌ها و واسط‌های فعلی را نداریم.

منابع:

www.oodesign.com
www.codeproject.com
www.dofactory.com

امتیاز شما به این مقاله:
نویسنده:

مطالب مرتبط