JavaRush /Java 博客 /Random-ZH /Adapter设计模式解决了什么问题?

Adapter设计模式解决了什么问题?

已在 Random-ZH 群组中发布
软件开发通常因相互协作的组件之间的不兼容性而变得复杂。例如,如果您需要将新库与用早期版本的 Java 编写的旧平台集成,您可能会遇到对象不兼容的情况,或者更准确地说,接口不兼容的情况。这种情况该怎么办?重写代码?但这是不可能的:分析系统将花费大量时间,或者工作的内部逻辑将被破坏。Adapter设计模式解决什么问题-1为了解决这个问题,他们提出了适配器模式,它可以帮助具有不兼容接口的对象一起工作。让我们看看如何使用它!

有关问题的更多详细信息

首先,让我们模拟旧系统的行为。假设它会产生上班或上学迟到的原因。为此,我们有一个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 界面。您无法重写代码:在大型应用程序中,此类更改可能需要很长时间或破坏应用程序逻辑。您可以建议引入主界面并增加层次结构: Adapter设计模式解决什么问题-3为此,您需要重命名界面Excuse。但在严肃的应用程序中,额外的层次结构是不可取的:引入公共根元素会破坏架构。应该实现一个中间类,允许以最小的损失使用新旧功能。简而言之,您需要一个适配器

适配器模式如何工作

适配器是一种中间对象,它使一个对象上的方法调用可以被另一个对象理解。让我们为我们的示例实现一个适配器并将其命名为Middleware。我们的适配器必须实现与其中一个对象兼容的接口。随它去Excuse。因此,Middleware它可以调用第一个对象的方法。 Middleware接收调用并将它们以兼容的格式传递给第二个对象。Middleware这就是带有methodsgenerateExcuse和的方法的实现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. 如果需要使用第三方类,但其接口与主应用程序不兼容。上面的示例显示了如何创建 shim 对象,该对象以目标对象可以理解的格式包装调用。

  2. 当多个现有子类必须具有共同功能时。最好使用适配器,而不是额外的子类(它们的创建将导致代码重复)。

的优点和缺点

优点:适配器向客户端隐藏从一个对象到另一对象的处理请求的细节。客户端代码不会考虑格式化数据或处理对目标方法的调用。它太复杂了,而且程序员很懒:) 缺点:项目的代码库因额外的类而变得复杂,如果存在大量不兼容点,它们的数量可能会增长到无法控制的大小。

不要与 Facade 和 Decorator 混淆

从表面上看,适配器可能会与外观和装饰器模式混淆。Adapter 和 Facade 之间的区别在于 Facade 引入了新的接口并包装了整个子系统。嗯,与适配器不同,装饰器更改对象本身,而不是接口。

分步实现算法

  1. 首先,确保存在该模式可以解决的问题。

  2. 定义一个客户端接口,将使用另一个类来代表该接口。

  3. 基于上一步中定义的接口实现适配器类。

  4. 在适配器类中,创建一个存储对象引用的字段。该引用在构造函数中传递。

  5. 在适配器中实现所有客户端接口方法。该方法可以:

    • 不做任何修改即可转接呼叫;

    • 更改数据、增加/减少目标方法的调用次数、进一步扩展数据的构成等。

    • 作为最后的手段,如果特定方法不兼容,则抛出 UnsupportedOperationException,严格需要记录该异常。

  6. 如果应用程序仅通过客户端接口使用适配器(如上例所示),则将来可以轻松扩展适配器。

当然,设计模式并不是包治百病的灵丹妙药,但在它的帮助下你可以优雅地解决不同接口的对象不兼容的问题。了解基本模式的开发人员比那些只知道如何编写算法的开发人员要高出几个步骤,因为创建严肃的应用程序需要它们。重用代码变得不那么困难,维护也变得很愉快。这就是今天的全部内容!但我们很快就会继续熟悉不同的设计模式:)
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION