设计模式之适配器模式

适配器模式

设计意图

将一个类的接口转换成你希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适用场景

  1. 我们有个专利续费的功能,分为单个续费和批量续费,单个续费的API接口在很早之前就开发的了,最新需求是批量续费功能,而我们批量续费是支持单个专利号的续费。这个时候呢,APP的单个续费功能早已上线,且不能影响早期版本的APP的单个专利续费功能(也是因为批量续费内部处理的稳定性也比单个得到了提升),但需要将单个续费计划迁移到批量续费的逻辑,但是早期的单个专利续费和现在的专利续费返回参数也有部分不同。在这种情况下,我们就要对单个续费进行批量续费的兼容,此时,就可以采用适配器设计模式。(业务型适配,重构);

思路和因素

  • 谁要被适配,适配器接口就继承谁,因为我们要始终保持被适配对象不变;
  • 适配器模式一般不是在初次设计就设计好的,一般在后期项目维护或者重构中才会用到;

步骤

  1. 声明一个中间类,我们下文称为适配器;
  2. 让适配器继承被适配类的接口;
  3. 在适配器中声明一个属性,类型为适配类的接口类型。(属性代理手法)

上代码

  1. 我们先定义两个已存在且需要被兼容的接口和实现
    ① 被适配类:
/**
 * 单个专利续费接口(举例说明,实际业务不是这么简单几个接口)
 */
public interface SinglePatent {

    /**
     * 专利的续费年限获取
     *
     * @param pId 一个专利号
     */
    String[] getYear(String pId);

    /**
     * 专利的续费金额计算
     *
     * @param pId   专利号
     * @param years 多年数组
     * @return 总金额
     */
    double getRenewalAmount(String pId, String[] years);
}

// 接口的实现
public class SinglePatentImpl implements SinglePatent {

    @Override
    public String[] getYear(String pId) {
        return new String[]{"5", "6", "7"}; // 假设那个专利号有这些年份
    }

    @Override
    public double getRenewalAmount(String pId, String[] years) {
        Map pId_data = bigData.apply(pId);// 从数据中心获取数据
        double count = 0D;
        for (String year : years) {
            count += pId_data.getOrDefault(year, 0D);
        }
        return count;
    }

    // 模拟,当作数据库或数据中心,我要调用它
    private static final Function> bigData =
            (String id) -> {
                Map data = new HashMap<>(); // 模拟续费年份价格
                data.put("5", 650.0D);
                data.put("6", 900.0D);
                data.put("7", 1200.0D);
                return data;
            };
}

② 适配类:

/**
 * 批量专利续费接口(举例说明,实际业务不是这么简单几个接口)
 */
public interface BatchPatent {

    /**
     * 专利的续费年限获取
     *
     * @param pIds 专利号
     * @return 专利号和年份
     */
    Map> getYears(String[] pIds);

    /**
     * 专利的续费金额计算
     *
     * @param pIdAndYears 专利号和年份
     * @return 专利号和金额
     */
    Map getRenewalAmounts(Map> pIdAndYears);
}

// 接口的实现
public class BatchPatentImpl implements BatchPatent {

    @Override
    public Map> getYears(String[] pIds) {
        Map> resultMap = new HashMap<>(4);
        resultMap.put("pid_0", Arrays.asList("1", "2"));
        resultMap.put("pid_1", Arrays.asList("2", "3", "4"));
        resultMap.put("pid_2", Arrays.asList("5", "6"));
        return resultMap;
    }

    @Override
    public Map getRenewalAmounts(Map> pIdAndYears) {
        Map resultMap = new HashMap<>(4);
        pIdAndYears.forEach((pId, years) -> {
            Map pId_data = bigData.apply(pId);// 从数据中心获取数据
            double count = 0D;
            for (String year : years) {
                count += pId_data.getOrDefault(year, 0D);
            }
            resultMap.put(pId, count);
        });
        return resultMap;
    }

    // 模拟,当作数据库或数据中心,我要调用它
    private static final Function> bigData =
            (String id) -> {
                Map data = new HashMap<>(); // 模拟续费年份价格,共用一份数据,避免讲述复杂
                data.put("1", 300.0D);
                data.put("2", 400.0D);
                data.put("3", 450.0D);
                data.put("4", 500.0D);
                data.put("5", 650.0D);
                data.put("6", 900.0D);
                return data;
            };
}
  1. 准备一个适配类:声明一个中间类,并继承被适配类的接口,同时去注入一个需要接口类型的属性
/**
 * 单个到批量的适配器接口(我这里额外把他封装了一个接口)
 */
public interface SingleToBatchAdapter extends SinglePatent {
}

// 声明一个中间类,我们下文称为适配器,在这里SingleToBatchAdapterImpl代表适配器
public class SingleToBatchAdapterImpl implements SingleToBatchAdapter { // 让适配器继承被适配类的接口

    // 在适配器中声明一个属性,类型为适配类的接口类型
    private final BatchPatent batchPatent; // 也可以采用spring注入

    public SingleToBatchAdapterImpl(BatchPatent batchPatent) {
        this.batchPatent = batchPatent;
    }

    @Override
    public String[] getYear(String pId) {
        Map> years = batchPatent.getYears(new String[]{pId});
        return years.get(pId).toArray(new String[]{});
    }

    @Override
    public double getRenewalAmount(String pId, String[] years) {
        // 构建适配类的入参
        Map> pIdAndYears = new HashMap<>(2);
        pIdAndYears.put(pId, new ArrayList<>(Arrays.asList(years)));
        // 进行适配
        Map renewalAmounts = batchPatent.getRenewalAmounts(pIdAndYears);
        // 处理适配类的返回
        return renewalAmounts.get(pId);
    }
}
  1. 测试适配器能否正常工作:日常编写测试类是重构的必备技能
    // 这里直接对适配器测试的
    public static void main(String[] args) {
        BatchPatentImpl batchPatent = new BatchPatentImpl();
        SingleToBatchAdapter singleToBatchAdapter = new SingleToBatchAdapterImpl(batchPatent);
        String[] pid_0s = singleToBatchAdapter.getYear("pid_1");// 这里定义的默认值
        System.out.println(new ArrayList<>(Arrays.asList(pid_0s)));
        double amount = singleToBatchAdapter.getRenewalAmount("pid_1", pid_0s);
        System.out.println(amount);
    }
  1. 重构 被适配类代码:
    这里分为两种:
  • 一种是适配器适配了所有的逻辑,那就直接将适配器当作新逻辑,直接将上层代码指向适配器(或直接继承父类),但是我个人不推荐;
  • 另一种是你原来的被适配类里面还有一些前置逻辑需要被处理,适配的是部分逻辑,则需要将适配器也注入到原来的被适配器类;
// 第一种就形式上面的测试方法,就忽略了

// 第二种,重构后
public class SinglePatentImpl implements SinglePatent {

    private final SingleToBatchAdapter singleToBatchAdapter = new SingleToBatchAdapterImpl(new BatchPatentImpl());

    /**
     * 专利的续费年限获取
     *
     * @param pId 一个专利号
     */
    @Override
    public String[] getYear(String pId) {
        // TODO 这里还可以做一些校验等处理,coding。。。
        String[] year = singleToBatchAdapter.getYear(pId);
        // TODO 这里还可以做一些原本具有的结果等处理,coding。。。
        return year;
    }

    /**
     * 专利的续费金额计算
     *
     * @param pId   专利号
     * @param years 多年数组
     * @return 总金额
     */
    @Override
    public double getRenewalAmount(String pId, String[] years) {
        return singleToBatchAdapter.getRenewalAmount(pId, years);
    }
}
  1. 再次测试业务主类,直到没有变动原有的兼容性,完成

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