软件设计模式白话文系列(七)适配器模式


1、描述

适配器模式顾名思义就是将某个类的接口转换成客户端期望的另一个接口表示。适配器模式可以消除由于接口不匹配所造成的类兼容性问题。

2、适用性

客户端需要调用现有的业务类,但此业务类的接口又不适用客户端的调用,这时就可以使用适配器模式,提供一个适配器类来达到目的。

3、实现逻辑

适配器模式一般包括下面三种角色类:

  • 目标接口类:定义客户端需要的接口规范。
  • 适配者类:现有的业务类。
  • 适配器类:将现有业务类的接口转换为适合客户端调用的接口

适配器模式有两种实现方式:

一种是继承现有业务类(适配者类)并实现目标接口类,在实现目标接口时调用父类方法,我们称之为类适配器。但这种适配器缺点很明显,继承会使现有的业务类(适配者类)接口对适配器类完全暴露,使得适配器具有现有接?类的全部功能,破坏了适配者类的封装性。

通常情况下我们会使用另一种对象适配器,对象适配只需实现目标接口,持有一个适配者类的实例并扩展其方法。这样做的好处是客户端调用适配器类时只能调用自己需要的接口,保证了原有接口的封装性。

4、实战代码

现有用户业务类存在用户 id 查询用户和列表查询返回用户实例对象或其实例集合,但现在新增业务需要通过 id 查询用户信息并返回 JSON 字符串。

4.1 对象适配器

下面代码实现:

/**
 * 用户类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 06:00:46
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Member {
    private Long id;
    private String name;
}

/**
 * 适配者类原有接口 在适配器模式中无实际左右 可以忽略
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 21:21:36
 */
public interface MemberService {

    Member findMemberById(Long id);

    List listMember();
}

/**
 * 适配者类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 21:24:49
 */
public class MemberServiceImpl implements MemberService {
    /**
     * 模拟 DB 初始化点数据
     */
    public static final Map db = new HashMap<>();

    static {
        db.put(1L, new Member(1L, "张三"));
        db.put(2L, new Member(2L, "李四"));
    }

    @Override
    public Member findMemberById(Long id) {
        return db.get(id);
    }

    @Override
    public List listMember() {
        return new ArrayList<>(db.values());
    }
}

/**
 * 目标接口类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 21:21:36
 */
public interface ClientService {

    String findJsonById(Long id);
}

/**
 * 适配器类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 21:27:46
 */
public class MemberServiceAdapter implements ClientService {
    private MemberService memberService;

    public MemberServiceAdapter(MemberService memberService) {
        this.memberService = memberService;
    }

    @Override
    public String findJsonById(Long id) {
        Member member = memberService.findMemberById(id);
        String jsonStr = JSONUtil.toJsonStr(member);
        return jsonStr;
    }
}

/**
 * 测试类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 21:29:56
 */
public class Client {
    public static void main(String[] args) {
        MemberServiceAdapter adapter = new MemberServiceAdapter(new MemberServiceImpl());
        System.out.println(adapter.findJsonById(1L));
    }
}

执行结果:

4.2 类适配器

这里这提供适配器类和测试类,其余类同对象适配器一致。

/**
 * 适配器类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 21:27:46
 */
public class MemberServiceAdapter extends MemberServiceImpl implements ClientService {
    @Override
    public String findJsonById(Long id) {
        Member member = super.findMemberById(id);
        String jsonStr = JSONUtil.toJsonStr(member);
        return jsonStr;
    }
}

/**
 * 测试类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 21:29:56
 */
public class Client {
    public static void main(String[] args) {
        MemberServiceAdapter adapter = new MemberServiceAdapter();
        System.out.println(adapter.findJsonById(1L));
        System.out.println(adapter.listMember());
    }
}

执行结果:

从结果可以看出,类适配器确实达到了我们需要的效果,实现了通过 id 过去对象 JSON 字符串的功能。但是同时也把我们不打算提供给客户端的获取全部列表暴露了出来。 违反了合成复用原则

5、 适配器模式和代理模式

5.1 相同点

适配器模式和代理模式结构同属结构型模式。两者都是通过增加一层中介层(代理模式增加代理类,适配器模式增加适配器类)来实现对原有类的扩展。

5.2 不相同点

两者应用场景有明显不同,适配器模式主要针对新旧接口不一致时导致的客户端无法正常调用的情况,因为我们旧接口类可能存在某种耦合而导致无法重构,为了使用旧接口的某些功能,而创建出来的转换器使旧接口转换成能被客户端使用的新接口。

而代理模式的主要作用是为了不把具体实现暴露出去,且通过代理类做一些处理。代理类接口和原有接口需要保证完全原一致。