JavaRush /وبلاگ جاوا /Random-FA /الگوی طراحی آداپتور چه مشکلاتی را حل می کند؟

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

در گروه منتشر شد
توسعه نرم افزار اغلب به دلیل ناسازگاری بین اجزایی که با یکدیگر کار می کنند پیچیده می شود. به عنوان مثال، اگر نیاز به ادغام یک کتابخانه جدید با یک پلتفرم قدیمی نوشته شده در نسخه های قبلی جاوا داشته باشید، ممکن است با ناسازگاری اشیا یا به طور دقیق تر، رابط ها مواجه شوید. در این مورد چه باید کرد؟ کد را دوباره بنویسیم؟ اما این غیرممکن است: تجزیه و تحلیل سیستم زمان زیادی می برد یا منطق داخلی کار شکسته می شود. الگوی طراحی آداپتور چه مشکلاتی را حل می کند - 1برای حل این مشکل، آنها الگوی Adapter را ارائه کردند که به اشیا با رابط های ناسازگار کمک می کند تا با هم کار کنند. بیایید ببینیم چگونه از آن استفاده کنیم!

جزئیات بیشتر در مورد مشکل

ابتدا بیایید رفتار سیستم قدیمی را شبیه سازی کنیم. فرض کنید دلایلی برای دیر رسیدن به محل کار یا مدرسه ایجاد می کند. برای انجام این کار، ما یک اینترفیس داریم که Excuseشامل متدها generateExcuse()و likeExcuse().dislikeExcuse()
public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
این رابط توسط کلاس پیاده سازی می شود WorkExcuse:
public class WorkExcuse implements Excuse {
   private String[] reasonOptions = {"по невероятному стечению обстоятельств у нас в доме закончилась горячая вода и я ждал, пока солнечный свет, сконцентрированный через лупу, нагреет кружку воды, чтобы я мог умыться.",
   "искусственный интеллект в моем будильнике подвел меня и разбудил на час раньше обычного. Поскольку сейчас зима, я думал что еще ночь и уснул. Дальше все How в тумане.",
   "предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице."};
   private String[] sorryOptions = {"Это, конечно, не повторится, мне очень жаль.", "Прошу меня извинить за непрофессиональное поведение.", "Нет оправдания моему поступку. Я недостоин этой должности."};

   @Override
   public String generateExcuse() { // Случайно выбираем отговорку из массива
       String result = "Я сегодня опоздал, потому что " + reasonOptions[(int) Math.round(Math.random() + 1)] + "\n" +
               sorryOptions[(int) Math.round(Math.random() + 1)];
       return result;
   }

   @Override
   public void likeExcuse(String excuse) {
       // Дублируем элемент в массиве, чтобы шанс его выпадения был выше
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Удаляем элемент из массива
   }
}
بیایید مثال را آزمایش کنیم:
Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
نتیجه:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице. 
Прошу меня извинить за непрофессиональное поведение.
حالا بیایید تصور کنیم که شما سرویس را راه اندازی کردید، آمار جمع آوری کردید و متوجه شدید که اکثر کاربران این سرویس را دانشجویان دانشگاه تشکیل می دهند. برای بهبود آن برای نیازهای این گروه، شما یک سیستم تولید بهانه را از یک توسعه دهنده دیگر به طور خاص برای او سفارش دادید. تیم توسعه تحقیقاتی انجام داد، رتبه بندی ها را گردآوری کرد، هوش مصنوعی را به هم متصل کرد، ادغام با ترافیک، آب و هوا و غیره را اضافه کرد. اکنون شما یک کتابخانه برای ایجاد بهانه برای دانش آموزان دارید، اما رابط برای تعامل با آن متفاوت است - StudentExcuse:
public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
اینترفیس دارای دو روش است: generateExcuse, که یک بهانه ایجاد می کند و dislikeExcuse, که بهانه را مسدود می کند تا در آینده ظاهر نشود. یک کتابخانه شخص ثالث برای ویرایش بسته است - نمی توانید کد منبع آن را تغییر دهید. در نتیجه، در سیستم شما دو کلاس وجود دارد که اینترفیس را پیاده سازی می کند Excuseو یک کتابخانه با یک کلاس SuperStudentExcuseکه اینترفیس را پیاده سازی می کند StudentExcuse:
public class SuperStudentExcuse implements StudentExcuse {
   @Override
   public String generateExcuse() {
       // Логика нового функционала
       return "Невероятная отговорка, адаптированная под текущее состояние погоды, пробки or сбои в расписании общественного транспорта.";
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Добавляет причину в черный список
   }
}
کد را نمی توان تغییر داد. طرح فعلی به این صورت خواهد بود: الگوی طراحی Adapter - 2 چه مشکلاتی را حل می کند؟این نسخه از سیستم فقط با رابط Excuse کار می کند. شما نمی توانید کد را بازنویسی کنید: در یک برنامه بزرگ، چنین تغییراتی می تواند مدت زیادی طول بکشد یا منطق برنامه را خراب کند. می توانید معرفی رابط اصلی و افزایش سلسله مراتب را پیشنهاد دهید: الگوی طراحی آداپتور چه مشکلاتی را حل می کند - 3برای انجام این کار، باید نام رابط را تغییر دهید Excuse. اما سلسله مراتب اضافی در کاربردهای جدی نامطلوب است: معرفی یک عنصر ریشه مشترک معماری را می شکند. یک کلاس متوسط ​​باید پیاده سازی شود که امکان استفاده از عملکردهای جدید و قدیمی را با حداقل تلفات فراهم کند. به طور خلاصه، شما به یک آداپتور نیاز دارید .

نحوه عملکرد الگوی آداپتور

آداپتور یک شیء میانی است که فراخوانی متدهای یک شی را برای دیگری قابل درک می کند. بیایید یک آداپتور برای مثال خود پیاده سازی کنیم و آن را فراخوانی کنیم Middleware. آداپتور ما باید رابطی را اجرا کند که با یکی از اشیاء سازگار باشد. بگذار باشد Excuse. به لطف این، Middlewareمی تواند متدهای شی اول را فراخوانی کند. Middlewareتماس ها را دریافت می کند و آنها را با فرمت سازگار به شی دوم ارسال می کند. Middlewareاجرای یک متد با متدها generateExcuseو به این صورت است dislikeExcuse:
public class Middleware implements Excuse { // 1. Middleware становится совместимым с an objectом WorkExcuse через интерфейс Excuse

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) { // 2. Получаем ссылку на адаптируемый an object
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse(); // 3. Адаптер реализовывает метод интерфейса
   }

    @Override
    public void dislikeExcuse(String excuse) {
        // Метод предварительно помещает отговорку в черный список БД,
        // Затем передает ее в метод dislikeExcuse an object superStudentExcuse.
    }
   // Метод likeExcuse появятся позже
}
تست (در کد مشتری):
public class Test {
   public static void main(String[] args) {
       Excuse excuse = new WorkExcuse(); // Создаются an objectы классов,
       StudentExcuse newExcuse = new SuperStudentExcuse(); // Которые должны быть совмещены.
       System.out.println("Обычная причина для работника:");
       System.out.println(excuse.generateExcuse());
       System.out.println("\n");
       Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Оборачиваем новый функционал в an object-адаптер
       System.out.println("Использование нового функционала с помощью адаптера:");
       System.out.println(adaptedStudentExcuse.generateExcuse()); // Адаптер вызывает адаптированный метод
   }
}
نتیجه:
Обычная причина для работника:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице.
Нет оправдания моему поступку. Я недостоин этой должности. Использование нового функционала с помощью адаптера
بهانه ای باورنکردنی، سازگار با شرایط آب و هوایی فعلی، ترافیک یا اختلال در برنامه حمل و نقل عمومی. این روش generateExcuseبه سادگی فراخوانی را به یک شی دیگر منتقل می کند، بدون اینکه تغییر شکل دهد. این روش dislikeExcuseنیازمند قرار دادن بهانه در لیست سیاه پایگاه داده بود. پردازش داده های میانی اضافی دلیل محبوبیت الگوی آداپتور است. اما در مورد روشی likeExcuseکه در اینترفیس است Excuse، اما در نه StudentExcuse؟ این عملیات در عملکرد جدید پشتیبانی نمی شود. برای چنین موردی، آنها با یک استثنا آمدند UnsupportedOperationException: اگر عملیات درخواستی پشتیبانی نشود، پرتاب می شود. بیایید از این استفاده کنیم. پیاده سازی کلاس جدید به این صورت است Middleware:
public class Middleware implements Excuse {

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) {
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse();
   }

   @Override
   public void likeExcuse(String excuse) {
       throw new UnsupportedOperationException("Метод likeExcuse не поддерживается в новом функционале");
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Метод обращается за дополнительной информацией к БД,
       // Затем передает ее в метод dislikeExcuse an object superStudentExcuse.
   }
}
در نگاه اول، این راه حل موفق به نظر نمی رسد، اما شبیه سازی عملکرد می تواند به وضعیت پیچیده تری منجر شود. اگر مشتری مراقب باشد و آداپتور به خوبی مستند باشد، این راه حل قابل قبول است.

زمان استفاده از آداپتور

  1. اگر نیاز به استفاده از یک کلاس شخص ثالث دارید، اما رابط آن با برنامه اصلی سازگار نیست. مثال بالا نشان می دهد که چگونه یک شی شیم ایجاد می شود که فراخوانی ها را در قالبی قابل درک برای شی هدف قرار می دهد.

  2. هنگامی که چندین زیر کلاس موجود باید عملکرد مشترک داشته باشند. به جای زیر کلاس های اضافی (ایجاد آنها منجر به تکرار کد می شود)، بهتر است از یک آداپتور استفاده کنید.

مزایا و معایب

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

نباید با نما و دکوراتور اشتباه شود

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

الگوریتم پیاده سازی گام به گام

  1. ابتدا مطمئن شوید که مشکلی وجود دارد که این الگو می تواند حل کند.

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

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

  4. در کلاس آداپتور، فیلدی ایجاد کنید که ارجاع به شی را ذخیره کند. این مرجع در سازنده ارسال می شود.

  5. تمام روش های رابط مشتری را در آداپتور پیاده سازی کنید. روش می تواند:

    • انتقال تماس بدون تغییر؛

    • تغییر داده ها، افزایش/کاهش تعداد تماس ها به روش هدف، گسترش بیشتر ترکیب داده ها و غیره.

    • به عنوان آخرین راه حل، اگر یک روش خاص ناسازگار است، یک UnsupportedOperationException را پرتاب کنید، که به شدت نیاز به مستندسازی دارد.

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

البته، یک الگوی طراحی نوشدارویی برای همه مشکلات نیست، اما با کمک آن می توانید مشکل ناسازگاری اشیاء با رابط های مختلف را به زیبایی حل کنید. توسعه‌دهنده‌ای که الگوهای اولیه را می‌داند، چندین پله بالاتر از کسانی است که به سادگی می‌دانند چگونه الگوریتم‌ها را بنویسند، زیرا برای ایجاد برنامه‌های کاربردی جدی به آنها نیاز است. استفاده مجدد از کد دشوارتر می شود و حفظ آن لذت بخش است. برای امروز کافی است! اما به زودی آشنایی خود را با الگوهای مختلف طراحی ادامه خواهیم داد :)
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION