定义

Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.(将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。)

适配器模式又叫做变压器模式,也叫做包装模式(Wrapper)。配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

类图

类结构型模式

类结构型模式类图
类结构型模式类图

对象结构型模式

对象结构型模式类图
对象结构型模式类图

Target目标角色

该角色定义把其他类转换为何种接口,也就是我们的期望接口。

Adaptee源角色

你想把谁转换成目标角色,这个“谁”就是源角色,它是已经存在的、运行良好的类或对象,经过适配器角色的包装,它会成为一个崭新、靓丽的角色。

Adapter适配器角色

适配器模式的核心角色,其他两个角色都是已经存在的角色,而适配器角色是需要新建立的,它的职责非常简单:把源角色转换为目标角色,怎么转换?通过继承或是类关联的方式。

优缺点

优点

适配器模式可以让两个没有任何关系的类在一起运行,只要适配器这个角色能够搞定他们就成。

  • 增加了类的透明性:我们访问的Target目标角色,但是具体的实现都委托给了源角色,而这些对高层次模块是透明的,也是它不需要关心的。
  • 提高了类的复用度:源角色在原有的系统中还是可以正常使用,而在目标角色中也可以充当新的演员。
  • 灵活性非常好:某一天,突然不想要适配器,没问题,删除掉这个适配器就可以了,其他的代码都不用修改,基本上就类似一个灵活的构件,想用就用,不想就卸载。

缺点

类适配器模式的缺点:

对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。

对象适配器模式的缺点:

与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

应用

Spring中的Redis Session就是一个非常典型的示例,servlet有HttpSession,但是我们为了做session共享我们通用会借助redis来实现,所以Spring就在一个过滤器里做了一个适配器把HttpSession转成RedisSession存储在redis,当我们获取的时候再从redis获取session。

还有一个示例也是Spring MVC中的Handler,一个http请求进来,他的入参与出参接口都是固定HttpServletRequest,HttpServletResponse但是实际的处理handler可以是方法也可以是类或者其他,我们只能知道Handler是一个Object所以就需要一个适配器让我们的处理更加灵活。

实现

我们尝试把Spring里RedisSession做一个简化实现。

我们简化session的存储以及他的对象信息:sessionId以及存储的session属性以及session创建时间。

public interface HttpSession {

   String getSessionId();

   Map<String, Object> getAttribute();

   long getCreationTime();
}

同样我们也简化原本的session的tomcat实现StandardSession

public class StandardSession implements HttpSession {
   private String id;

   private Map<String, Object> sessionAttrs = new HashMap<>();

   private long creationTime = Instant.now().getEpochSecond();

   public StandardSession(String id) {
      this.id = id;
   }

   public StandardSession() {
   }

   @Override
   public String getSessionId() {
      return id;
   }

   @Override
   public Map<String, Object> getAttribute() {
      return sessionAttrs;
   }

   @Override
   public long getCreationTime() {
      return creationTime;
   }
}

定义一个RedisSession表示存储在redis里的对象

public class RedisSession {

   public MapSession session;

   private boolean isNew;

   private String originalSessionId;

   static class MapSession {
      private String id;

      private Map<String, Object> sessionAttrs = new HashMap<>();

      private Instant creationTime = Instant.now();

      public MapSession(String id, Map<String, Object> sessionAttrs, Instant creationTime) {
         this.id = id;
         this.sessionAttrs = sessionAttrs;
         this.creationTime = creationTime;
      }

      public String getId() {
         return id;
      }

      public Map<String, Object> getSessionAttrs() {
         return sessionAttrs;
      }

      public Instant getCreationTime() {
         return creationTime;
      }

   }

   public MapSession getSession() {
      return session;
   }
}

当一个Http请求进来拿到的session类型一定是HttpSession,那么我们就需要定义一个适配器把RedisSession转成HttpSession

public class SessionAdapter implements HttpSession{
 // 通过组合的方式来实现包装需要被适配的类
   private RedisSession session;

   public SessionAdapter(RedisSession session) {
      this.session = session;
   }

   @Override
   public String getSessionId() {
      return session.getSession().getId();
   }

   @Override
   public Map<String, Object> getAttribute() {
      return session.getSession().getSessionAttrs();
   }

   @Override
   public long getCreationTime() {
      return session.getSession().getCreationTime().getEpochSecond();
   }
}

看看客户端的获取方式:

public class Client {
   public static void main(String[] args) {
      HttpSession session = new StandardSession("aebsss123");
      session.getSessionId();

      //使用redis之后
      HttpSession session1 = new SessionAdapter(getSessionFromRedis());
      session1.getSessionId();
   }

   private static RedisSession getSessionFromRedis() {
      return new RedisSession();
   }
}

我们只需要从适配器里取对象就可以完成了!

以上是通过适配器类的组合方式,还有一种通过继承来实现,大致看一下代码实现:

public class Adaptee {

    public void adapteeRequest() {
        System.out.println("被适配者的方法");
    }

}
public interface Target {
//    需要执行的目标方法
    void request();
}
public class Adapter extends Adaptee implements Target{

    @Override
    public void request() {
        //。。。增加新的业务
        super.adapteeRequest();
        //。。。增加新的业务
    }

}

总结

适配器模式是一个补偿模式,或者说是一个“补救”模式,通常用来解决接口不相容的问题。如果是业务设计初期肯定是避免使用这种方式的,或者像Spring这种想要实现多样化的如Redis存储session就可以借助适配器把固定的HttpSession转成不同的对象以适配不同的业务需求。

参考

https://design-patterns.readthedocs.io/zh_CN/latest/structural_patterns/adapter.html
《设计模式之禅》