软件开发通常因相互协作的组件之间的不兼容性而变得复杂。例如,如果您需要将新库与用早期版本的 Java 编写的旧平台集成,您可能会遇到对象不兼容的情况,或者更准确地说,接口不兼容的情况。这种情况该怎么办?重写代码?但这是不可能的:分析系统将花费大量时间,或者工作的内部逻辑将被破坏。为了解决这个问题,他们提出了适配器模式,它可以帮助具有不兼容接口的对象一起工作。让我们看看如何使用它!
有关问题的更多详细信息
首先,让我们模拟旧系统的行为。假设它会产生上班或上学迟到的原因。为此,我们有一个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) {
// Добавляет причину в черный список
}
}
代码无法更改。当前的方案将如下所示: 该版本的系统仅适用于 Excuse 界面。您无法重写代码:在大型应用程序中,此类更改可能需要很长时间或破坏应用程序逻辑。您可以建议引入主界面并增加层次结构: 为此,您需要重命名界面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.
}
}
乍一看,这个解决方案似乎并不成功,但模拟功能可能会导致更复杂的情况。如果客户细心并且适配器有详细记录,则此解决方案是可以接受的。
何时使用适配器
-
如果需要使用第三方类,但其接口与主应用程序不兼容。上面的示例显示了如何创建 shim 对象,该对象以目标对象可以理解的格式包装调用。
-
当多个现有子类必须具有共同功能时。最好使用适配器,而不是额外的子类(它们的创建将导致代码重复)。
的优点和缺点
优点:适配器向客户端隐藏从一个对象到另一对象的处理请求的细节。客户端代码不会考虑格式化数据或处理对目标方法的调用。它太复杂了,而且程序员很懒:) 缺点:项目的代码库因额外的类而变得复杂,如果存在大量不兼容点,它们的数量可能会增长到无法控制的大小。不要与 Facade 和 Decorator 混淆
从表面上看,适配器可能会与外观和装饰器模式混淆。Adapter 和 Facade 之间的区别在于 Facade 引入了新的接口并包装了整个子系统。嗯,与适配器不同,装饰器更改对象本身,而不是接口。分步实现算法
-
首先,确保存在该模式可以解决的问题。
-
定义一个客户端接口,将使用另一个类来代表该接口。
-
基于上一步中定义的接口实现适配器类。
-
在适配器类中,创建一个存储对象引用的字段。该引用在构造函数中传递。
-
在适配器中实现所有客户端接口方法。该方法可以:
-
不做任何修改即可转接呼叫;
-
更改数据、增加/减少目标方法的调用次数、进一步扩展数据的构成等。
-
作为最后的手段,如果特定方法不兼容,则抛出 UnsupportedOperationException,严格需要记录该异常。
-
-
如果应用程序仅通过客户端接口使用适配器(如上例所示),则将来可以轻松扩展适配器。
GO TO FULL VERSION