设计模式九(适配器模式)

适配器模式

定义

适配器模式(Adapter Pattern)又叫做变压器模式,它的功能是将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而导致无法在一起工作的两个类能够一起工作,属于结构型设计模式。

也就是说,当前系统存在两种接口A和 B,客户只支持访问A接口,但里当前系统没有A接口对象,但是有 B接口对象,但客户无法识别 B接口,因此需要通过一个适配器C,将 B接口内容转换成A接口,从而使得客户能够从A接口获取得到B接口内容。

在软件开发中,基本上任何问题都可以通过增加一个中间层进行解决。适配器模式 其实就是一个中间层。综上,适配器模式 其实起着转化/委托的作用,将一种接口转化为另一种符合需求的接口。

适配器模式的应用场景

提供一个转换器(适配器),将当前系统存在的一个对象转化为客户端能够问的接口对像,适配器适用于以下几种业务场景:

  1. 已经存在的类,它的方法和需求不匹配(方法结果相同或相似)的情况;
  2. 适配器模式不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案,有点亡羊补牢的感觉。

生活中也有非常多的应用场景,例如电源插转换头、手机充电转换头、显示器转接头。适配器模式一般包含三种角色:

  1. 目标角色( Target ):也就是我们期望的接口;
  2. 源角色(Adaptee):存在于系统中,内容满足客户需求(需转换),但接口不四配的接口实例;
  3. 适配器(Adapter):将源角色(Adape)转化为目标角色(aget)的类实例;

适配器模式各角色之间的关系如下:

假设当前系统中,客户端需要访问的是Target接口,但Target接口没有一个实例符合需求,br>
而 Adaptee实例符合需求;但客户端无法直接使用Adaptee(接口不兼容);br>
因此,我们需要一个适配器(Adapter)来进行中转,让Adaptee 能转化为Target接口形式

适配器模式有3种形式:类适配器、对象适配器,接口适配器。

类适配器

类适配器的原理就是通过继承来实现适配器功能。来看一个例子:我们平时的民用电都是 220V交流电,但我们的手机锂电池使用的是 5V的直流电,因此我们给手机充电时就需要使用电源适配器来进行转换,首先来创建Adaptee角色,需要被转换的对象 AC220,代表220V交流电:

public class AC220 {
	
	public int outputAC220V() {
		int output = 220;
		System.out.println("输出电压" + output + "V");
		return output;
	}
}

创建 Target 角色 DC5接口,表示 5V直流电的标准:

public interface DC5 {
	
	/**
	 *  输出 5V 直流电
	 * @return
	 */
	int output5V();
}

创建 Adapter角色电源适配器 PowerAdapter 类:

public class PowerAdapter extends AC220 implements DC5 {

	@Override
	public int output5V() {
		int adapterInput = super.outputAC220V();
		int adapterOutput = adapterInput / 44;
		System.out.println("使用Adapter输入AC" + adapterInput + "V,输出DC" + adapterOutput + "V");
		return adapterOutput;
	}
}

测试:

public static void main(String[] args) {
		DC5 adapter = new PowerAdapter();
		adapter.output5V();
	}

查看运行结果:
设计模式九(适配器模式)_第1张图片通过增加 PowerAdapter 适配器,实现了二者的兼容。

对象适配器

对象适配的原理就是通过组合来实现适配器功能。具体做法:让 Adapter实现 Target 接口,然后内部持有Adaptee 实例,然后再Target接口规定的方法内转换 Adaptee。只需要修改 Adapter 类:

public class PowerAdapter implements DC5 {
	private AC220 ac220;

	public PowerAdapter(AC220 ac220) {
		this.ac220 = ac220;
	}

	@Override
	public int output5V() {
		int adapterInput = ac220.outputAC220V();
		int adapterOutput = adapterInput / 44;
		System.out.println("使用Adapter输入AC" + adapterInput + "V,输出DC" + adapterOutput + "V");
		return adapterOutput;
	}
}

测试:

public static void main(String[] args) {
		DC5 adapter = new PowerAdapter(new AC220());
		adapter.output5V();
	}

设计模式九(适配器模式)_第2张图片

接口适配器

接口适配器的关注点与类适配器和对象适配器的关注点不太一样,类适配器和对象适配器着重于将系统存在的一个角色(Adaptee)转化成目标接口(Target)所需内容,而口适配器的使用场景是解决接口方法过多,如果直接实现接口,部么类会多出许多空实现的方法,类显得很臃肿,此时,使用接口适配器就能让我们只实现我们需要的接口方法,目标更清晰。

接口适配器的主要原理就是利用抽象类实现接口,并且空实现接口众多方法。来看例子:

public interface DC {

	int output5V();
	
    int output12V();
    
    int output24V();
    
    int output36V();
}
public class AC220 {

	public int outputAC220V() {
		int output = 220;
		System.out.println("输出电压" + 220 + "V");
		return output;
	}
}
public class PowerAdapter implements DC {
	private AC220 ac220;

	public PowerAdapter(AC220 ac220) {
		this.ac220 = ac220;
	}

	@Override
	public int output5V() {
		int adapterInput = ac220.outputAC220V();
		int adapterOutput = adapterInput / 44;
		System.out.println("使用Adapter输入AC" + adapterInput + "V,输出DC" + adapterOutput + "V");
		return adapterOutput;
	}

	@Override
	public int output12V() {
		return 0;
	}

	@Override
	public int output24V() {
		return 0;
	}

	@Override
	public int output36V() {
		return 0;
	}
}
public static void main(String[] args) {
        DC adapter = new PowerAdapter(new AC220());
        adapter.output5V();
    }
重构第三方登录自由适配的业务场景

下面我们利用适配器模式来解决一个实际问题,很早以前开发的老系统都有登录接口,随着业务的发展,现在需要接入第三方登录,比如 QQ、微信、微博等,同时需要保留用户名密码的登录方式。可以这样改:

@Data
@AllArgsConstructor
public class ResultMsg {

    private int code;
    private String msg;
    private Object data;

	public ResultMsg(int code, String msg) {
		this.code = code;
		this.msg = msg;
	}
}

人员信息 POJO:

@Data
public class Member {

    private String username;
    private String password;
    private String mid;
    private String info;
}

假设老系统的登录逻辑为:

public class PassportService {

    /**
     * 注册方法
     * @param username
     * @param password
     * @return
     */
    public ResultMsg regist(String username,String password){
        return  new ResultMsg(200,"注册成功",new Member());
    }

    /**
     * 登录的方法
     * @param username
     * @param password
     * @return
     */
    public ResultMsg login(String username,String password){
        return null;
    }

}

为了遵循开闭原则,运行稳定的代码,我们不改,首先创建 Target 角色的IPassportForThird接口:

public interface IPassportForThird {

	/**
	 * QQ 登录
	 *
	 * @param openId
	 * @return
	 */
	ResultMsg loginForQQ(String openId);

	/**
	 * 微信 登录
	 *
	 * @param openId
	 * @return
	 */
	ResultMsg loginForWechat(String openId);

	ResultMsg loginForToken(String token);

	ResultMsg loginForTelphone(String phone, String code);

}

然后创建适配器角色进行兼容:

public class PassportForThirdAdapter extends PassportService implements IPassportForThird {

	@Override
	public ResultMsg loginForQQ(String openId) {
		return loginForRegist(openId, null);
	}

	@Override
	public ResultMsg loginForWechat(String openId) {
		return loginForRegist(openId, null);
	}

	@Override
	public ResultMsg loginForToken(String token) {
		return loginForRegist(token, null);
	}

	@Override
	public ResultMsg loginForTelphone(String phone, String code) {
		return loginForRegist(phone, null);
	}

	
	private ResultMsg loginForRegist(String username, String password) {
		if (null == password) {
			password = "THIRD_EMPTY";
		}
		super.regist(username, password);
		return super.login(username, password);
	}
}

测试:

public static void main(String[] args) {
		PassportForThirdAdapter adapter = new PassportForThirdAdapter();
		adapter.login("zhangsan", "123456");
		adapter.loginForQQ("xxsdfas11234454353");
		adapter.loginForWechat("ggdsawera56e234523");
	}

通过这么一个简单的适配,完成了代码的兼容。当然,我们的代码还可以更加优雅,根据不同的登录方式,创建不同的 Adapter,首先创建ILoginAdapter接口:

public interface ILoginAdapter {

	/**
	 * 校验是否支持适配
	 * @param object
	 * @return
	 */
	boolean support(Object object);

	ResultMsg login(String id, Object adapter);
}

然后,创建一个抽象类继承老的登录方式,同时实现新加的登录接口,然后分别实现不同的登录适配:

public abstract class AbstraceAdapter extends PassportService implements ILoginAdapter {

	protected ResultMsg loginForRegist(String username, String password) {
		if (null == password) {
			password = "THIRD_EMPTY";
		}
		super.regist(username, password);
		return super.login(username, password);
	}
}

public class LoginForQQAdapter extends AbstraceAdapter {

	@Override
	public boolean support(Object adapter) {
		return adapter instanceof LoginForQQAdapter;
	}

	@Override
	public ResultMsg login(String id, Object adapter) {
		if (!support(adapter)) {
			return null;
		}
		return super.loginForRegist(id, null);
	}
}

public class LoginForWechatAdapter extends AbstraceAdapter {

	@Override
	public boolean support(Object adapter) {
		return adapter instanceof LoginForWechatAdapter;
	}

	@Override
	public ResultMsg login(String id, Object adapter) {
		if (!support(adapter)) {
			return new ResultMsg(403,"无法适配");
		}
		return super.loginForRegist(id, null);
	}
}

创建适配器PassportForThirdAdapter,实现IPassportForThird接口,完成适配:

public class PassportForThirdAdapter implements IPassportForThird {

	@Override
	public ResultMsg loginForQQ(String openId) {
		return processLogin(openId, LoginForQQAdapter.class);
	}

	@Override
	public ResultMsg loginForWechat(String openId) {

		return processLogin(openId, LoginForWechatAdapter.class);

	}

	@Override
	public ResultMsg loginForToken(String token) {

		return processLogin(token, LoginForTokenAdapter.class);
	}

	@Override
	public ResultMsg loginForTelphone(String phone, String code) {
		return processLogin(phone, LoginForTelAdapter.class);
	}


	private ResultMsg processLogin(String id, Class<? extends ILoginAdapter> clazz) {
		try {
			ILoginAdapter adapter = clazz.newInstance();
			if (adapter.support(adapter)) {
				return adapter.login(id, adapter);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

}

测试:

public static void main(String[] args) {
        
    	IPassportForThird adapter = new PassportForThirdAdapter();
        adapter.loginForQQ("sdfasdfasfasfas");
    }
适配器模式在源码中的应用

Spring 中适配器模式也应用的非常广泛,例如:Spring AOP 中的 AdvisorAdapter类,来看下源码:

package org.springframework.aop.framework.adapter;

import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.Advisor;

public interface AdvisorAdapter {
    boolean supportsAdvice(Advice var1);

    MethodInterceptor getInterceptor(Advisor var1);
}

设计模式九(适配器模式)_第3张图片来看一下MethodBeforeAdviceAdapter的源码:

package org.springframework.aop.framework.adapter;

import java.io.Serializable;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.Advisor;
import org.springframework.aop.MethodBeforeAdvice;

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
    MethodBeforeAdviceAdapter() {
    }

    public boolean supportsAdvice(Advice advice) {
        return advice instanceof MethodBeforeAdvice;
    }

    public MethodInterceptor getInterceptor(Advisor advisor) {
        MethodBeforeAdvice advice = (MethodBeforeAdvice)advisor.getAdvice();
        return new MethodBeforeAdviceInterceptor(advice);
    }
}

Spring 会根据不同的 AOP 配置来确定使用对应的Advice。再来看 SpringMVC的 HandlerAdapter:

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;

public interface HandlerAdapter {
    boolean supports(Object var1);

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

    long getLastModified(HttpServletRequest var1, Object var2);
}

设计模式九(适配器模式)_第4张图片

适配器模式与装饰器模式对比
装饰器模式 适配器模式
形式 是一种非常特别的适配器模式 没有层级关系,装饰器模式有层级关系
定义 装饰器和被装饰器都实现同一个接口,主要目的是为了扩展之后依旧保留 OOP 关系 适配器和被适配者没有必然联系,通常是采用继承或者代理的形式进行包装
关系 满足 is-a 关系 满足 has-a 关系
功能 注重覆盖、扩展 注重兼容、转换
设计 前置考虑 后置考虑
适配器模式的优缺点

优点:

  1. 能提高类的透明性和复用,现有的类需要复用,但不需要改变;
  2. 目标类与适配器类解耦,提高程序的扩展性;
  3. 在很多业务场景中,符合开闭原则。

缺点:

  1. 适配器编写过程需要全面考虑,可能会增加系统的复杂性;
  2. 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。

上一篇:享元模式

你可能感兴趣的:(设计模式,设计模式,适配器模式)