设计模式之适配器模式

适配器模式

设计意图

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

适用场景

  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<String, Double> pId_data = bigData.apply(pId);// 从数据中心获取数据
        double count = 0D;
        for (String year : years) {
            count += pId_data.getOrDefault(year, 0D);
        }
        return count;
    }

    // 模拟,当作数据库或数据中心,我要调用它
    private static final Function<String, Map<String, Double>> bigData =
            (String id) -> {
                Map<String, Double> 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<String, List<String>> getYears(String[] pIds);

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

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

    @Override
    public Map<String, List<String>> getYears(String[] pIds) {
        Map<String, List<String>> 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<String, Double> getRenewalAmounts(Map<String, List<String>> pIdAndYears) {
        Map<String, Double> resultMap = new HashMap<>(4);
        pIdAndYears.forEach((pId, years) -> {
            Map<String, Double> 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<String, Map<String, Double>> bigData =
            (String id) -> {
                Map<String, Double> 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<String, List<String>> years = batchPatent.getYears(new String[]{pId});
        return years.get(pId).toArray(new String[]{});
    }

    @Override
    public double getRenewalAmount(String pId, String[] years) {
        // 构建适配类的入参
        Map<String, List<String>> pIdAndYears = new HashMap<>(2);
        pIdAndYears.put(pId, new ArrayList<>(Arrays.asList(years)));
        // 进行适配
        Map<String, Double> 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. 再次测试业务主类,直到没有变动原有的兼容性,完成
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容