【设计模式实践笔记】第六节:适配器模式

233 阅读5分钟

适配器模式

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

如果说适配器你不熟的话,那充电器总应该知道,仔细看一下手机、笔记本充电器的说明会发现上面写了「电源适配器」这个名词。

在现实生活中,电源适配器把输入的220V高压电,转换输出为20V左右的低压电来给笔记本进行供电,为我们解决笔记本电脑的接口不能直接使用220V家用电的问题,它解决了“家用电”和“设备用电”不兼容的问题。

苹果电源适配器

在工程领域,我们编码中使用适配器模式也是一个道理,目的就是为了解决两个类接口不兼容的问题,通过一个中间层来让一个类「适配」另一个类的接口。

那么问题来了,如果不使用的话会怎么样?

让我们先来看一个例子。

老板:接个新物流商

假设我们在做一个电商系统,初期我们只对接了一家物流商,没有做过多的设计优化,但是考虑到提高系统内聚,还是会对物流下单服务进行了抽象。是的,你没看错,这就是抽象工厂模式那一节的代码,所以说设计模式都是配合使用的,很少单打独斗

物流下单服务:

public interface LogisticsService {
​
    /**
     * 创建物流订单
     */
    String createOrder(String goodsName);
​
    /**
     * 创建生鲜订单
     */
    String createFreshOrder(String goodsName);
}
​
public class LogisticsServiceImpl implements LogisticsService {
    private static ShunfengClient shunfengClient = new ShunfengClient();
​
    @Override
    public String createOrder(String goodsName) {
        return shunfengClient.createOrder(1, goodsName);
    }
​
    @Override
    public String createFreshOrder(String goodsName) {
        return shunfengClient.createOrder(2, goodsName);
    }
}

物流商客户端:

// 值得说明的是这是一个模拟的顺丰客户端,实际上各物流商会提供一个SDK来简化我们的接入
public class ShunfengClient {
​
    /**
     * 创建顺丰订单
     *
     * @param type 1=普通快递,2=生鲜物流
     * @param content 包裹内容物
     */
    public String createOrder(int type, String content) {
        System.out.println("创建顺丰" + (type == 1 ? "快递" : "生鲜物流") + "订单成功,配送物品=" + content);
        return "SF" + System.currentTimeMillis();
    }
}

测试代码:

public class LogisticsServiceImplTest {
    private static LogisticsService logisticsService = new LogisticsServiceImpl();
​
    @Test
    public void createOrder() {
        String trackingNo = logisticsService.createOrder("手机");
        System.out.println(trackingNo);
    }
​
    @Test
    public void createFreshOrder() {
        String trackingNo = logisticsService.createFreshOrder("帝王蟹");
        System.out.println(trackingNo);
    }
}

运行结果:

创建顺丰快递订单成功,配送物品=手机
SF1658750143796
创建顺丰生鲜物流订单成功,配送物品=帝王蟹
SF1658750143798

如果不使用就会在核心代码里面耦合外部系统提供的类。这么写乍一看还没啥毛病,运行正常高效,也做了简单的抽象把实现跟接口分离,不过一旦要拓展更多的物流商,势必要在核心层编写大量代码来对外部类做兼容操作。

面对这种情况,我们可以增加一层防腐层,一开始就对外部物流商提供的服务做好抽象,作为核心业务的依赖,在防腐层来对接和适配,这样即使增加再多的物流商,也不会腐化核心层代码。

防腐层

在防腐层里我们使用的就是适配器模式。

上面讲的抽象物流商服务,是我们定义的业务接口,在系统域内应该依赖此接口。防腐层创建各种适配器来将物流商提供的接口转换成我们自定义的业务接口。

适配器模式

这么做有两个好处:

  1. 单一职责原则,将接口转换从业务代码中解耦出来。
  2. 开闭原则,新增适配器无需改动业务代码。

重构优化

引入防腐层以后,我们的代码可以进行一些小范围重构来优化之前的实现。

// 顺丰物流适配器
public class ShunfengLogisticsAdapter implements LogisticsService {
    private ShunfengClient shunfengClient = new ShunfengClient();
​
    // 创建速运订单
    @Override
    public String createOrder(String goodsName) {
        return shunfengClient.createOrder(1, goodsName);
    }
​
    // 创建生鲜订单
    @Override
    public String createFreshOrder(String goodsName) {
        return shunfengClient.createOrder(2, goodsName);
    }
}
​
// 京东物流适配器
public class JingdongLogisticsAdapter implements LogisticsService {
    private JingdongClient jingdongClient = new JingdongClient();
​
    // 创建速运订单
    @Override
    public String createOrder(String goodsName) {
        return jingdongClient.createOrder(goodsName);
    }
​
    // 创建生鲜订单
    @Override
    public String createFreshOrder(String goodsName) {
        return jingdongClient.createFreshOrder(goodsName);
    }
}

现实中各种物流商的服务通常各不相同,模拟京东客户端如下:

public class JingdongClient {
​
    /**
     * 创建京东订单
     *
     * @param content 包裹内容物
     */
    public String createOrder(String content) {
        System.out.println("创建京东快送订单成功,配送物品=" + content);
        return "JD" + System.currentTimeMillis();
    }
​
    /**
     * 创建京东订单
     *
     * @param content 包裹内容物
     */
    public String createFreshOrder(String content) {
        System.out.println("创建京东生鲜物流订单成功,配送物品=" + content);
        return "JD" + System.currentTimeMillis();
    }
}

测试代码如下:

public class LogisticsServiceAdapterlTest {
​
    @Test
    public void createOrder() {
        LogisticsService logisticsService = new ShunfengLogisticsAdapter();
        String trackingNo = logisticsService.createOrder("手机");
        System.out.println(trackingNo);
​
        logisticsService = new JingdongLogisticsAdapter();
        trackingNo = logisticsService.createOrder("手机");
        System.out.println(trackingNo);
    }
​
    @Test
    public void createFreshOrder() {
        LogisticsService logisticsService = new ShunfengLogisticsAdapter();
        String trackingNo = logisticsService.createFreshOrder("帝王蟹");
        System.out.println(trackingNo);
​
        logisticsService = new JingdongLogisticsAdapter();
        trackingNo = logisticsService.createFreshOrder("帝王蟹");
        System.out.println(trackingNo);
    }
}

测试结果:

创建顺丰快递订单成功,配送物品=手机
SF1659695541527
创建京东快送订单成功,配送物品=手机
JD1659695541528
创建顺丰生鲜物流订单成功,配送物品=帝王蟹
SF1659695541529
创建京东生鲜物流订单成功,配送物品=帝王蟹
JD1659695541530

在实战中适配器模式经常跟工厂模式和门面模式配合使用,在第二节的抽象工厂模式中即是如此。

小结

适配器模式通过解耦变与不变,解决了新老接口适配的问题,最大的好处是实现了领域间的解耦,这也是在领域驱动设计中比较推崇适配器来进行各方交互的原因。

实践出真知,让我们一起加油吧!

关注「Java实践笔记」公众号获取全部源代码!

版权声明:本文为公众号「Java实践笔记」的原创文章,转载请联系作者,附上原文链接及本声明。