适配器模式(Adapter Pattern)

490 阅读6分钟

定义

适配器模式(Adapter Pattern)又叫做变压器模式,它的功能是将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配二导致无法在以前工作的两个类,能够一起工作。在不改变原有代码的情况下,对原有功能的一种转换、扩展,使得原有的功能能够满足新的需求。

三种写法

  1. 类适配器
  2. 对象适配器
  3. 接口适配器

一、类适配器(系统存在的角色转换成目标需要的内容)

场景说明:现有220V电压。需求是可以输出5V电压同时也可以输出220V电压。
需要通过220V电压转换成5V电压输出,因此在不改变220V电压类的情况下,
增加5V电压。新增5V电压接口,新增适配器类,适配器类继承220V电压,实现5V电压接口。
在适配器类处理220V转换5V的逻辑,最终实现可以输出5V电压。

image.png

/**
 * 原有类
 */
public class AC220 {
    public int outputAC220V(){
        int output = 220;
        System.out.println("输出电压" + output + "V");
        return output;
    }
}
/**
 * 新增方法(模板类)
 */
public interface DC5 {
    int output5V ();

}
/**
 * 类适配器类
 */
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,输出AC"+adapterOutput+"V");
        return adapterOutput;
    }
}
/**
 * 测试
 */
public class Test {
    public static void main(String[] args) {
        DC5 adapter = new PowerAdapter();
        adapter.output5V();
    }
}

二、对象适配器(系统存在的角色转换成目标需要的内容)

场景说明:需要通过220V电压转换成5V电压,只需要5V电压,而不保留22V电压。
新增5V电压接口,新增适配器类构造方法入参为220V电压,实现DC5V接口,
在实现的output5V()方法里面实现220V转5V的逻辑。客户端调用适配器,只有DC5V的方法。

image.png

public class AC220 {
    public int outputAC220V(){
        int output = 220;
        System.out.println("输出电压" + output + "V");
        return output;
    }
}
/**
 * 新增方法(目标类)
 */
public interface DC5 {
    int output5V();

}
/**
 * 对象适配器
 * 符合最小知道原则,适配器只有AC的方法
 */
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,输出AC"+adapterOutput+"V");
        return adapterOutput;
    }
}
public class Test {
    public static void main(String[] args) {
        DC5 adapter = new PowerAdapter(new AC220());
        adapter.output5V();
    }
}

三、接口适配器

接口适配器

image.png

/**
 * 原有类
 */
public class AC220 {
    public int outputAC220V(){
        int output = 220;
        System.out.println("输出电压" + output + "V");
        return output;
    }
}
public interface DC {

    int output5V();
    int output12V();
    int output24V();
    int output36();
}
/**
 * 2、对象适配器
 * 违背了接口隔离和单一职责原则,把所以功能聚集在一起减少类的关联关系
 */
public class PowerAdapter implements DC {

    private AC220 ac220;

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

    @Override
    public int output5V() {
        int output = ac220.outputAC220V() / 44;
        return output;
    }

    @Override
    public int output12V() {
        int output = ac220.outputAC220V() / 18;
        return output;
    }

    @Override
    public int output24V() {
        int output = ac220.outputAC220V() / 9;
        return output;
    }

    @Override
    public int output36() {
        int output = ac220.outputAC220V() / 6;
        return output;
    }
}
public class Test {
    public static void main(String[] args) {
        DC adapter = new PowerAdapter(new AC220());
        adapter.output5V();
    }
}

四、登录例子

背景:原项目只有注册,登录账号,现有的注册登录方法很稳定,现在需要增加第三方登录,底层还是调用旧的注册登录方法。

方式一

缺点:不容易再度扩展,此时如果再增加,微博登录...等等。需要改接口,改动适配器。不符合开闭原则。

image.png

public class ResultMsg {

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

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

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}
/**
 * 原有类
 */
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 new ResultMsg(200,"登录成功",new Member());
    }
}
/**
 * 目标接口
 */
public interface IPassportForOther {
    ResultMsg loginForQQ(String openId);
    ResultMsg loginForWechat(String openId);
    ResultMsg loginForToken(String token);
    ResultMsg loginForTelphone(String phone,String code);
}
/**
 * 目标接口适配器
 */
public class PassportForOtherAdapter extends PassportService implements IPassportForOther {
    @Override
    public ResultMsg loginForQQ(String openId) {
        // TODO 这里要跟腾讯SDK对接,代码逻辑很长,导致这个类很臃肿
        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) {
        // TODO 这里要跟第三方短信平台对接,代码逻辑很长,导致这个类很臃肿
        return loginForRegist(phone,null);
    }

    /**
     * 第三方登录不可能用密码登录
     * 所以约定第三方登录的时候设置一个默认的第三方通用THIRD_EMPTY,后台做一个标记如果是THIRD_EMPTY这个密码,则不走正常流程
     * 如果有用户设置密码时不能设置这个密码,提示密码无效,请重新设置
     * @param username
     * @param password
     * @return
     */
    private ResultMsg loginForRegist(String username,String password) {
        // 约定,密码为空,使用内置登录密码,并
        if(null == password) {
            password = "THIRD_EMPTY";
        }
        super.regist(username,password);
        return super.login(username,password);
    }
}
/**
 * 测试类
 */
public class Test {
    public static void main(String[] args) {
        PassportForOtherAdapter adapter = new PassportForOtherAdapter();
        adapter.login("admin","123456");
        adapter.loginForQQ("sdfgsfghs");// QQ提供的唯一的开发ID
        adapter.loginForWechat("falkjkjnkvf");// 微信提供的唯一的开放ID
    }
}

进阶

方式一的实现方法不符合开闭原则。进阶版思路:用两个适配器,一个总适配器,起到调度的作用,每种登录方式都写一个适配器,增加登录方式,增加适配器类,不需要改动原有的代码,只需增加类。

image.png

/**
 * 原有类
 */
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 new ResultMsg(200,"登录成功",new Member());
    }
}
public class ResultMsg {

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

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

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}
/**
 * 登录适配器接口
 */
public interface ILoginAdapter {
    // 适配器是否匹配
    boolean support(Object object);
    // 调用第三方SDK获取登录的token
    String token();
}
public interface IPassportForOther {
    ResultMsg loginForOther();
}
public class LoginForQQAdapter implements ILoginAdapter{

    @Override
    public boolean support(Object object) {
        return false;
    }

    @Override
    public String token() {
        System.out.println("发送http请求");
        System.out.println("QQ登录,QQ返回的唯一ID->");
        String username = "admin";
        return username;
    }
}
/**
 * 对外提供的适配器
 */
public class PassportForOtherAdapter extends PassportService implements IPassportForOther{

    private ILoginAdapter loginAdapter;
    public PassportForOtherAdapter(ILoginAdapter loginAdapter) {
        this.loginAdapter = loginAdapter;
    }

    /**
     * 第三方登录
     * @return
     */
    @Override
    public ResultMsg loginForOther() {
        return loginForRegist(loginAdapter.token(),null);
    }

    /**
     * 第三方登录不可能用密码登录,模拟注册之后再登录
     * 所以约定第三方登录的时候设置一个默认的第三方通用THIRD_EMPTY,后台做一个标记如果是THIRD_EMPTY这个密码,则不走正常流程
     * 如果有用户设置密码时不能设置这个密码,提示密码无效,请重新设置
     *
     * @param username
     * @param password
     * @return
     */
    protected ResultMsg loginForRegist(String username, String password) {
        if (null == password) {
            password = "THIRD_EMPTY";
        }
        super.regist(username, password);
        return super.login(username, password);
    }

}
public class Test {

    public static void main(String[] args) {
        IPassportForOther adapter = new PassportForOtherAdapter(new LoginForQQAdapter());
        adapter.loginForOther();
    }
}