美文网首页
优雅的实现接口转换——Java 适配器模式详解

优雅的实现接口转换——Java 适配器模式详解

作者: IT职业与自媒体思考 | 来源:发表于2020-06-28 21:42 被阅读0次

    项目4说的是适配器,Peter老师也总结一下Java中的适配器模式。

    适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

    在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。

    根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。

    角色

    Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。

    Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。

    Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。

    缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。缺省适配器模式是适配器模式的一种变体,其应用也较为广泛。在JDK类库的事件处理包java.awt.event中广泛使用了缺省适配器模式,如WindowAdapter、KeyAdapter、MouseAdapter等。

    示例

    类适配器

    首先有一个已存在的将被适配的类

    public class Adaptee {

    public voidadapteeRequest() {

    System.out.println("被适配者的方法");

        }

    }

    复制代码

    定义一个目标接口

    public interface Target {

        void request();

    }

    复制代码

    怎么才可以在目标接口中的 request() 调用 Adaptee 的 adapteeRequest() 方法呢?

    如果直接实现 Target 是不行的

    public class ConcreteTarget implements Target {

        @Override

    public voidrequest() {

    System.out.println("concreteTarget目标方法");

        }

    }

    复制代码

    如果通过一个适配器类,实现 Target 接口,同时继承了 Adaptee 类,然后在实现的 request() 方法中调用父类的 adapteeRequest() 即可实现

    public class Adapter extends Adaptee implements Target{

        @Override

    public voidrequest() {

            //...一些操作...

            super.adapteeRequest();

            //...一些操作...

        }

    }

    复制代码

    我们来测试一下

    public class Test {

        public static void main(String[] args) {

            Target target = new ConcreteTarget();

            target.request();

            Target adapterTarget = new Adapter();

            adapterTarget.request();

        }

    }

    复制代码

    输出

    concreteTarget目标方法

    被适配者的方法

    复制代码

    这样我们即可在新接口 Target 中适配旧的接口或类

    对象适配器

    对象适配器与类适配器不同之处在于,类适配器通过继承来完成适配,对象适配器则是通过关联来完成,这里稍微修改一下 Adapter 类即可将转变为对象适配器

    public class Adapter implements Target{

        // 适配者是对象适配器的一个属性

        private Adaptee adaptee = new Adaptee();

        @Override

    public voidrequest() {

            //...

            adaptee.adapteeRequest();

            //...

        }

    }

    复制代码

    注意这里的 Adapter 是将 Adaptee 作为一个成员属性,而不是继承它

    电压适配器

    再来一个好理解的例子,我们国家的民用电都是 220V,日本是 110V,而我们的手机充电一般需要 5V,这时候要充电,就需要一个电压适配器,将 220V 或者 100V 的输入电压变换为 5V 输出

    定义输出交流电接口,输出220V交流电类和输出110V交流电类

    public interface AC {

        int outputAC();

    }

    public class AC110 implements AC {

        public final int output = 110;

        @Override

    public intoutputAC() {

    returnoutput;

        }

    }

    public class AC220 implements AC {

        public final int output = 220;

        @Override

    public intoutputAC() {

    returnoutput;

        }

    }

    复制代码

    适配器接口,其中 support() 方法用于检查输入的电压是否与适配器匹配,outputDC5V() 方法则用于将输入的电压变换为 5V 后输出

    public interface DC5Adapter {

        boolean support(AC ac);

        int outputDC5V(AC ac);

    }

    复制代码

    实现中国变压适配器和日本变压适配器

    public class ChinaPowerAdapter implements DC5Adapter {

        public static final int voltage = 220;

        @Override

        public boolean support(AC ac) {

    return(voltage == ac.outputAC());

        }

        @Override

        public int outputDC5V(AC ac) {

            int adapterInput = ac.outputAC();

            //变压器...

            int adapterOutput = adapterInput / 44;

    System.out.println("使用ChinaPowerAdapter变压适配器,输入AC:"+ adapterInput +"V"+",输出DC:"+ adapterOutput +"V");

    returnadapterOutput;

        }

    }

    public class JapanPowerAdapter implements DC5Adapter {

        public static final int voltage = 110;

        @Override

        public boolean support(AC ac) {

    return(voltage == ac.outputAC());

        }

        @Override

        public int outputDC5V(AC ac) {

            int adapterInput = ac.outputAC();

            //变压器...

            int adapterOutput = adapterInput / 22;

    System.out.println("使用JapanPowerAdapter变压适配器,输入AC:"+ adapterInput +"V"+",输出DC:"+ adapterOutput +"V");

    returnadapterOutput;

        }

    }

    复制代码

    测试,准备中国变压适配器和日本变压适配器各一个,定义一个方法可以根据电压找到合适的变压器,然后进行测试

    public class Test {

        private List<DC5Adapter> adapters = new LinkedList<DC5Adapter>();

    publicTest() {

            this.adapters.add(new ChinaPowerAdapter());

            this.adapters.add(new JapanPowerAdapter());

        }

        // 根据电压找合适的变压器

        public DC5Adapter getPowerAdapter(AC ac) {

            DC5Adapter adapter = null;

    for(DC5Adapter ad : this.adapters) {

    if(ad.support(ac)) {

                    adapter = ad;

    break;

                }

            }

    if(adapter == null){

    throw new  IllegalArgumentException("没有找到合适的变压适配器");

            }

    returnadapter;

        }

        public static void main(String[] args) {

    Testtest= new Test();

            AC chinaAC = new AC220();

            DC5Adapter adapter = test.getPowerAdapter(chinaAC);

            adapter.outputDC5V(chinaAC);

            // 去日本旅游,电压是 110V

            AC japanAC = new AC110();

            adapter = test.getPowerAdapter(japanAC);

            adapter.outputDC5V(japanAC);

        }

    }

    复制代码

    输出

    使用ChinaPowerAdapter变压适配器,输入AC:220V,输出DC:5V

    使用JapanPowerAdapter变压适配器,输入AC:110V,输出DC:5V

    复制代码

    适配器模式总结

    主要优点

    将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。

    增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。

    灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。

    具体来说,类适配器模式还有如下优点:

    由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

    对象适配器模式还有如下优点:

    一个对象适配器可以把多个不同的适配者适配到同一个目标;

    可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。

    类适配器模式的缺点如下:

    对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;

    适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;

    在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。

    对象适配器模式的缺点如下:

    与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

    适用场景

    系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。

    想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

    源码分析适配器模式的典型应用

    spring AOP中的适配器模式

    在Spring的Aop中,使用的 Advice(通知) 来增强被代理类的功能。

    Advice的类型有:MethodBeforeAdvice、AfterReturningAdvice、ThrowsAdvice

    在每个类型 Advice 都有对应的拦截器,MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、ThrowsAdviceInterceptor

    Spring需要将每个 Advice 都封装成对应的拦截器类型,返回给容器,所以需要使用适配器模式对 Advice 进行转换

    三个适配者类 Adaptee 如下:

    public interface MethodBeforeAdvice extends BeforeAdvice {

        void before(Method var1, Object[] var2, @Nullable Object var3) throws Throwable;

    }

    public interface AfterReturningAdvice extends AfterAdvice {

        void afterReturning(@Nullable Object var1, Method var2, Object[] var3, @Nullable Object var4) throws Throwable;

    }

    public interface ThrowsAdvice extends AfterAdvice {

    }

    复制代码

    目标接口 Target,有两个方法,一个判断 Advice 类型是否匹配,一个是工厂方法,创建对应类型的 Advice 对应的拦截器

    public interface AdvisorAdapter {

        boolean supportsAdvice(Advice var1);

        MethodInterceptor getInterceptor(Advisor var1);

    }

    复制代码

    三个适配器类 Adapter 分别如下,注意其中的 Advice、Adapter、Interceptor之间的对应关系

    class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

    @Override

    public boolean supportsAdvice(Advice advice) {

    return(advice instanceof MethodBeforeAdvice);

    }

    @Override

    public MethodInterceptor getInterceptor(Advisor advisor) {

    MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();

    returnnew MethodBeforeAdviceInterceptor(advice);

    }

    }

    @SuppressWarnings("serial")

    class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {

    @Override

    public boolean supportsAdvice(Advice advice) {

    return(advice instanceof AfterReturningAdvice);

    }

    @Override

    public MethodInterceptor getInterceptor(Advisor advisor) {

    AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();

    returnnew AfterReturningAdviceInterceptor(advice);

    }

    }

    class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable {

    @Override

    public boolean supportsAdvice(Advice advice) {

    return(advice instanceof ThrowsAdvice);

    }

    @Override

    public MethodInterceptor getInterceptor(Advisor advisor) {

    returnnew ThrowsAdviceInterceptor(advisor.getAdvice());

    }

    }

    复制代码

    客户端 DefaultAdvisorAdapterRegistry

    public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {

        private final List<AdvisorAdapter> adapters = new ArrayList(3);

    publicDefaultAdvisorAdapterRegistry() {

            // 这里注册了适配器

            this.registerAdvisorAdapter(new MethodBeforeAdviceAdapter());

            this.registerAdvisorAdapter(new AfterReturningAdviceAdapter());

            this.registerAdvisorAdapter(new ThrowsAdviceAdapter());

        }

        public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {

            List<MethodInterceptor> interceptors = new ArrayList(3);

            Advice advice = advisor.getAdvice();

    if(advice instanceof MethodInterceptor) {

                interceptors.add((MethodInterceptor)advice);

            }

            Iterator var4 = this.adapters.iterator();

    while(var4.hasNext()) {

                AdvisorAdapter adapter = (AdvisorAdapter)var4.next();

    if(adapter.supportsAdvice(advice)) {  // 这里调用适配器方法

                    interceptors.add(adapter.getInterceptor(advisor));  // 这里调用适配器方法

                }

            }

    if(interceptors.isEmpty()) {

                throw new UnknownAdviceTypeException(advisor.getAdvice());

    }else{

    return(MethodInterceptor[])interceptors.toArray(new MethodInterceptor[0]);

            }

        }

        // ...省略...

    }   

    复制代码

    这里看 while 循环里,逐个取出注册的适配器,调用 supportsAdvice() 方法来判断 Advice 对应的类型,然后调用 getInterceptor() 创建对应类型的拦截器

    这里应该属于对象适配器模式,关键字 instanceof 可看成是 Advice 的方法,不过这里的 Advice 对象是从外部传进来,而不是成员属性

    spring JPA中的适配器模式

    在Spring的ORM包中,对于JPA的支持也是采用了适配器模式,首先定义了一个接口的 JpaVendorAdapter,然后不同的持久层框架都实现此接口。

    jpaVendorAdapter:用于设置实现厂商JPA实现的特定属性,如设置Hibernate的是否自动生成DDL的属性generateDdl;这些属性是厂商特定的,因此最好在这里设置;目前Spring提供 HibernateJpaVendorAdapter、OpenJpaVendorAdapter、EclipseLinkJpaVendorAdapter、TopLinkJpaVendorAdapter 四个实现。其中最重要的属性是 database,用来指定使用的数据库类型,从而能根据数据库类型来决定比如如何将数据库特定异常转换为Spring的一致性异常,目前支持如下数据库(DB2、DERBY、H2、HSQL、INFORMIX、MYSQL、ORACLE、POSTGRESQL、SQL_SERVER、SYBASE)

    public interface JpaVendorAdapter

    {

      // 返回一个具体的持久层提供者

      public abstract PersistenceProvider getPersistenceProvider();

      // 返回持久层提供者的包名

      public abstract String getPersistenceProviderRootPackage();

      // 返回持久层提供者的属性

      public abstract Map<String, ?> getJpaPropertyMap();

      // 返回JpaDialect

      public abstract JpaDialect getJpaDialect();

      // 返回持久层管理器工厂

      public abstract Class<? extends EntityManagerFactory> getEntityManagerFactoryInterface();

      // 返回持久层管理器

      public abstract Class<? extends EntityManager> getEntityManagerInterface();

      // 自定义回调方法

      public abstract void postProcessEntityManagerFactory(EntityManagerFactory paramEntityManagerFactory);

    }

    复制代码

    我们来看其中一个适配器实现类 HibernateJpaVendorAdapter

    public class HibernateJpaVendorAdapter extends AbstractJpaVendorAdapter {

        //设定持久层提供者

        private final PersistenceProvider persistenceProvider;

        //设定持久层方言

        private final JpaDialect jpaDialect;

    publicHibernateJpaVendorAdapter() {

            this.persistenceProvider = new HibernatePersistence();

            this.jpaDialect = new HibernateJpaDialect();

        }

        //返回持久层方言

    public PersistenceProvidergetPersistenceProvider() {

    returnthis.persistenceProvider;

        }

        //返回持久层提供者

    public StringgetPersistenceProviderRootPackage() {

    return"org.hibernate";

        }

        //返回JPA的属性

    public MapgetJpaPropertyMap() {

            Map jpaProperties = new HashMap();

    if(getDatabasePlatform() != null) {

    jpaProperties.put("hibernate.dialect", getDatabasePlatform());

    }elseif(getDatabase() != null) {

                Class databaseDialectClass = determineDatabaseDialectClass(getDatabase());

    if(databaseDialectClass != null) {

    jpaProperties.put("hibernate.dialect",

                            databaseDialectClass.getName());

                }

            }

    if(isGenerateDdl()) {

    jpaProperties.put("hibernate.hbm2ddl.auto","update");

            }

    if(isShowSql()) {

    jpaProperties.put("hibernate.show_sql","true");

            }

    returnjpaProperties;

        }

        //设定数据库

        protected Class determineDatabaseDialectClass(Database database)   

        {                                                                                     

    switch (1.$SwitchMap$org$springframework$orm$jpa$vendor$Database[database.ordinal()])

            {                                                                                   

    case1:

    returnDB2Dialect.class;

    case2:

    returnDerbyDialect.class;

    case3:

    returnH2Dialect.class;

    case4:

    returnHSQLDialect.class;

    case5:

    returnInformixDialect.class;

    case6:

    returnMySQLDialect.class;

    case7:

    returnOracle9iDialect.class;

    case8:

    returnPostgreSQLDialect.class;

    case9:

    returnSQLServerDialect.class;

    case10:

    returnSybaseDialect.class; }

    returnnull;

        }

        //返回JPA方言

    public JpaDialectgetJpaDialect() {

    returnthis.jpaDialect;

        }

        //返回JPA实体管理器工厂

    public ClassgetEntityManagerFactoryInterface() {

    returnHibernateEntityManagerFactory.class;

        }

        //返回JPA实体管理器

    public ClassgetEntityManagerInterface() {

    returnHibernateEntityManager.class;

        }

    }

    复制代码

    配置文件中可以这样指定

    </bean> 

    复制代码

    spring MVC中的适配器模式

    Spring MVC中的适配器模式主要用于执行目标 Controller 中的请求处理方法。

    在Spring MVC中,DispatcherServlet 作为用户,HandlerAdapter 作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller 作为需要适配的类。

    为什么要在 Spring MVC 中使用适配器模式?Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,需要的自行来判断,像下面这段代码一样:

    if(mappedHandler.getHandler() instanceof MultiActionController){

      ((MultiActionController)mappedHandler.getHandler()).xxx 

    }elseif(mappedHandler.getHandler() instanceof XXX){

        ... 

    }elseif(...){

      ... 

    复制代码

    这样假设如果我们增加一个 HardController,就要在代码中加入一行 if(mappedHandler.getHandler() instanceof HardController),这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。

    我们来看看源码,首先是适配器接口 HandlerAdapter

    public interface HandlerAdapter {

        boolean supports(Object var1);

        ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;

        long getLastModified(HttpServletRequest var1, Object var2);

    }

    复制代码

    现该接口的适配器每一个 Controller 都有一个适配器与之对应,这样的话,每自定义一个 Controller 需要定义一个实现 HandlerAdapter 的适配器。

    springmvc 中提供的 Controller 实现类有如下

    springmvc 中提供的 HandlerAdapter 实现类如下

    HttpRequestHandlerAdapter 这个适配器代码如下

    public class HttpRequestHandlerAdapter implements HandlerAdapter {

    publicHttpRequestHandlerAdapter() {

        }

        public boolean supports(Object handler) {

    returnhandler instanceof HttpRequestHandler;

        }

        public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

            ((HttpRequestHandler)handler).handleRequest(request, response);

    returnnull;

        }

        public long getLastModified(HttpServletRequest request, Object handler) {

    returnhandler instanceof LastModified ? ((LastModified)handler).getLastModified(request) : -1L;

        }

    }

    复制代码

    当Spring容器启动后,会将所有定义好的适配器对象存放在一个List集合中,当一个请求来临时,DispatcherServlet 会通过 handler 的类型找到对应适配器,并将该适配器对象返回给用户,然后就可以统一通过适配器的 hanle() 方法来调用 Controller 中的用于处理请求的方法。

    public class DispatcherServlet extends FrameworkServlet {

        private List<HandlerAdapter> handlerAdapters;

        //初始化handlerAdapters

        private void initHandlerAdapters(ApplicationContext context) {

            //..省略...

        }

        // 遍历所有的 HandlerAdapters,通过 supports 判断找到匹配的适配器

        protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {

    for(HandlerAdapter ha : this.handlerAdapters) {

    if(logger.isTraceEnabled()) {

    logger.trace("Testing handler adapter ["+ ha +"]");

    }

    if(ha.supports(handler)) {

    returnha;

    }

    }

    }

    // 分发请求,请求需要找到匹配的适配器来处理

    protected voiddoDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

    HttpServletRequest processedRequest = request;

    HandlerExecutionChain mappedHandler = null;

    // Determine handlerforthe current request.

    mappedHandler = getHandler(processedRequest);

    // 确定当前请求的匹配的适配器.

    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    ha.getLastModified(request, mappedHandler.getHandler());

    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        }

    // ...省略...

    }

    复制代码

    通过适配器模式我们将所有的 controller 统一交给 HandlerAdapter 处理,免去了写大量的 if-else 语句对 Controller 进行判断,也更利于扩展新的 Controller 类型。

    相关文章

      网友评论

          本文标题:优雅的实现接口转换——Java 适配器模式详解

          本文链接:https://www.haomeiwen.com/subject/khegfktx.html