CodeGym /Java 博客 /随机的 /适配器设计模式解决了什么问题?
John Squirrels
第 41 级
San Francisco

适配器设计模式解决了什么问题?

已在 随机的 群组中发布
需要协同工作的不兼容组件使软件开发变得更加困难。例如,如果您需要将新库与用早期 Java 版本编写的旧平台集成,您可能会遇到不兼容的对象,或者更确切地说是不兼容的接口。 适配器设计模式解决了什么问题? - 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[] excuses = {"in an incredible confluence of circumstances, I ran out of hot water and had to wait until sunlight, focused using a magnifying glass, heated a mug of water so that I could wash.",
   "the artificial intelligence in my alarm clock failed me, waking me up an hour earlier than normal. Because it is winter, I thought it was still nighttime and I fell back asleep. Everything after that is a bit hazy.",
   "my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia."};
   private String [] apologies = {"This will not happen again, of course. I'm very sorry.", "I apologize for my unprofessional behavior.", "There is no excuse for my actions. I am not worthy of this position."};

   @Override
   public String generateExcuse() { // Randomly select an excuse from the array
       String result = "I was late today because " + excuses[(int) Math.round(Math.random() + 1)] + "\\n" +
               apologies[(int) Math.round(Math.random() + 1)];
       return result;
   }

   @Override
   public void likeExcuse(String excuse) {
       // Duplicate the element in the array so that its chances of being chosen are higher
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Remove the item from the array
   }
}
让我们测试一下我们的例子:

Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
输出:

"I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
I apologize for my unprofessional behavior.
现在想象一下,您启动了一个找借口的服务,收集了统计数据,并注意到您的大多数用户都是大学生。为了更好地为这个群体服务,您要求另一位开发人员创建一个专门为大学生生成借口的系统。开发团队进行了市场调查,对借口进行排序,连接了一些人工智能,并将该服务与交通报告、天气预报等相结合。现在你有了一个为大学生找借口的图书馆,但它有一个不同的界面:StudentExcuse

public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
这个接口有两个方法:generateExcuse,生成一个借口,和dislikeExcuse,防止借口在未来再次出现。第三方库不能编辑,即你不能改变它的源代码。我们现在拥有的是一个包含两个实现接口的类的系统Excuse,以及一个包含一个SuperStudentExcuse实现StudentExcuse接口的类的库:

public class SuperStudentExcuse implements StudentExcuse {
   @Override
   public String generateExcuse() {
       // Logic for the new functionality
       return "An incredible excuse adapted to the current weather conditions, traffic jams, or delays in public transport schedules.";
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Adds the reason to a blacklist
   }
}
代码无法更改。当前的类层次结构如下所示: 适配器设计模式解决了什么问题? - 2该版本的系统仅适用于 Excuse 接口。您无法重写代码:在大型应用程序中,进行此类更改可能会成为一个漫长的过程或破坏应用程序的逻辑。我们可以引入一个基本接口并扩展层次结构: 适配器设计模式解决了什么问题? - 3为此,我们必须重命名该Excuse接口。但是额外的层次结构在严肃的应用程序中是不可取的:引入一个公共根元素会破坏体系结构。您应该实现一个中间类,让我们以最小的损失同时使用新旧功能。简而言之,您需要一个适配器

适配器模式背后的原理

适配器是一种中间对象,它允许一个对象的方法调用被另一个对象理解。让我们为示例实现一个适配器并将其命名为Middleware. 我们的适配器必须实现与其中一个对象兼容的接口。顺其自然吧Excuse。这允许Middleware调用第一个对象的方法。 Middleware接收调用并以兼容的方式将它们转发给第二个对象。这是Middleware使用generateExcuseanddislikeExcuse方法的实现:

public class Middleware implements Excuse { // 1. Middleware becomes compatible with WorkExcuse objects via the Excuse interface

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) { // 2. Get a reference to the object being adapted
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse(); // 3. The adapter implements an interface method
   }

    @Override
    public void dislikeExcuse(String excuse) {
        // The method first adds the excuse to the blacklist,
        // Then passes it to the dislikeExcuse method of the superStudentExcuse object.
    }
   // The likeExcuse method will appear later
}
测试(在客户端代码中):

public class Test {
   public static void main(String[] args) {
       Excuse excuse = new WorkExcuse(); // We create objects of the classes
       StudentExcuse newExcuse = new SuperStudentExcuse(); // that must be compatible.
       System.out.println("An ordinary excuse for an employee:");
       System.out.println(excuse.generateExcuse());
       System.out.println("\n");
       Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Wrap the new functionality in the adapter object
       System.out.println("Using new functionality with the adapter:");
       System.out.println(adaptedStudentExcuse.generateExcuse()); // The adapter calls the adapted method
   }
}
输出:

An ordinary excuse for an employee:
I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
There is no excuse for my actions. I am not worthy of this position. Using new functionality with the adapter:
一个适应当前天气状况、交通拥堵或公共交通时刻表延误的令人难以置信的借口。该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("The likeExcuse method is not supported by the new functionality");
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // The method accesses a database to fetch additional information,
       // and then passes it to the superStudentExcuse object's dislikeExcuse method.
   }
}
乍一看,这个解决方案似乎不太好,但模仿功能会使情况复杂化。如果客户端注意,并且适配器有很好的文档,这样的解决方案是可以接受的。

何时使用适配器

  1. 当需要使用第三方类,但其接口与主应用不兼容时。上面的示例显示了如何创建一个适配器对象,该对象以目标对象可以理解的格式包装调用。

  2. 当几个现有的子类需要一些通用功能时。与其创建额外的子类(这会导致代码重复),不如使用适配器。

的优点和缺点

优点:适配器向客户端隐藏了处理从一个对象到另一个对象的请求的细节。客户端代码不会考虑格式化数据或处理对目标方法的调用。太复杂了,程序员又懒了:) 缺点:项目的代码库被额外的类复杂化了。如果你有很多不兼容的接口,那么额外类的数量就会变得难以管理。

不要将适配器与外观或装饰器混淆

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

逐步算法

  1. 首先,请确保您遇到了此模式可以解决的问题。

  2. 定义将用于与不兼容对象间接交互的客户端接口。

  3. 让适配器类继承上一步定义的接口。

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

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

    • 传递呼叫而不做任何更改

    • 修改或补充数据,增加/减少目标方法的调用次数等。

    • 在极端情况下,如果特定方法仍然不兼容,则抛出 UnsupportedOperationException。不受支持的操作必须严格记录。

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

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