軟體設計模式白話文系列(七)適配器模式

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<Member> listMember();
}

/**
 * 適配者類
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 21:24:49
 */
public class MemberServiceImpl implements MemberService {
    /**
     * 模擬 DB 初始化點數據
     */
    public static final Map<Long, Member> 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<Member> 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 不相同點

兩者應用場景有明顯不同,適配器模式主要針對新舊介面不一致時導致的客戶端無法正常調用的情況,因為我們舊介面類可能存在某種耦合而導致無法重構,為了使用舊介面的某些功能,而創建出來的轉換器使舊介面轉換成能被客戶端使用的新介面。

而代理模式的主要作用是為了不把具體實現暴露出去,且通過代理類做一些處理。代理類介面和原有介面需要保證完全原一致。