適配器模式

適配器模式的應(yīng)用場(chǎng)景

適配器模式(Adapter Pattern)是指將一個(gè)類的接口轉(zhuǎn)換成客戶期望的另一個(gè)接口,使原本的接口不兼容的類可以一起工作,屬于結(jié)構(gòu)型設(shè)計(jì)模式。
適配器適用于以下幾種業(yè)務(wù)場(chǎng)景:
1、已經(jīng)存在的類,它的方法和需求不匹配(方法結(jié)果相同或相似)的情況。
2、適配器模式不是軟件設(shè)計(jì)階段考慮的設(shè)計(jì)模式,是隨著軟件維護(hù),由于不同產(chǎn)品、不同廠家造成功能類似而接口不相同情況下的解決方案。有點(diǎn)亡羊補(bǔ)牢的感覺。 生活中也非常的應(yīng)用場(chǎng)景,例如電源插轉(zhuǎn)換頭、手機(jī)充電轉(zhuǎn)換頭、顯示器轉(zhuǎn)接頭。



在中國(guó)民用電都是 220V 交流電,但我們手機(jī)使用的鋰電池使用的 5V 直流電。因此,我 們給手機(jī)充電時(shí)就需要使用電源適配器來進(jìn)行轉(zhuǎn)換。下面我們有代碼來還原這個(gè)生活場(chǎng) 景,創(chuàng)建 AC220 類,表示 220V 交流電:

public class AC220 {

    public int outputAC220V(){
        int output = 220;
        System.out.println("輸出電流" + output + "V");
        return output;
    }
}

創(chuàng)建 DC5 接口,表示 5V 直流電的標(biāo)準(zhǔn):

public interface DC5 {
    int outoupDC5V();
}

創(chuàng)建電源適配器 PowerAdapter 類:

public class PowerAdapter implements DC5 {

   private AC220 ac220;

    public PowerAdapter(AC220 ac220) {
        this.ac220 = ac220;
    }

    public int outoupDC5V() {
        int adapterInput = ac220.outputAC220V();
        int adapterOutput = adapterInput / 44;
        System.out.println("使用PowerAdapter輸入AC:" + adapterInput + "V,輸出DC:" + adapterOutput + "V");
        return adapterOutput;
    }
}

客戶端測(cè)試代碼:

public class PowerAdapterTest {
    public static void main(String[] args) {
        DC5 dc5 = new PowerAdapter(new AC220());
        dc5.outoupDC5V();
    }
}

上面的案例中,通過增加 PowerAdapter 電源適配器,實(shí)現(xiàn)了二者的兼容。

重構(gòu)第三登錄自由適配的業(yè)務(wù)場(chǎng)景

下面我們來一個(gè)實(shí)際的業(yè)務(wù)場(chǎng)景,利用適配模式來解決實(shí)際問題。年紀(jì)稍微大一點(diǎn)的小 伙伴一定經(jīng)歷過這樣一個(gè)過程。我們很早以前開發(fā)的老系統(tǒng)應(yīng)該都有登錄接口,但是隨 著業(yè)務(wù)的發(fā)展和社會(huì)的進(jìn)步,單純地依賴用戶名密碼登錄顯然不能滿足用戶需求了?,F(xiàn) 在,我們大部分系統(tǒng)都已經(jīng)支持多種登錄方式,如 QQ 登錄、微信登錄、手機(jī)登錄、微 博登錄等等,同時(shí)保留用戶名密碼的登錄方式。雖然登錄形式豐富了,但是登錄后的處理邏輯可以不必改,同樣是將登錄狀態(tài)保存到 session,遵循開閉原則。首先創(chuàng)建統(tǒng)一的 返回結(jié)果 ResultMsg 類:

public class ResultMsg {

    private int code;
    private String msg;
    private Object data;

    public ResultMsg(int code, String msg, Object data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

假設(shè)老系統(tǒng)的登錄邏輯 SiginService:

public class SiginService {

    /**
     * 注冊(cè)方法
     * @param username
     * @param password
     * @return
     */
    public ResultMsg regist(String username,String password){
        return  new ResultMsg(200,"注冊(cè)成功",new Member());
    }


    /**
     * 登錄的方法
     * @param username
     * @param password
     * @return
     */
    public ResultMsg login(String username,String password){
        return null;
    }

}

為了遵循開閉原則,老系統(tǒng)的代碼我們不會(huì)去修改。那么下面開啟代碼重構(gòu)之路,先創(chuàng) 建 Member 類:

public class Member {

    private String username;
    private String password;
    private String mid;
    private String info;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getMid() {
        return mid;
    }

    public void setMid(String mid) {
        this.mid = mid;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }
}

創(chuàng)建一個(gè)新的類繼承原來的邏輯,運(yùn)行非常穩(wěn)定的代碼我們不去改動(dòng)

public class SinginForThirdService extends SiginService {

    public ResultMsg loginForQQ(String openId){
        //1、openId是全局唯一,我們可以把它當(dāng)做是一個(gè)用戶名(加長(zhǎng))
        //2、密碼默認(rèn)為QQ_EMPTY
        //3、注冊(cè)(在原有系統(tǒng)里面創(chuàng)建一個(gè)用戶)

        //4、調(diào)用原來的登錄方法

        return loginForRegist(openId,null);
    }

    public ResultMsg loginForWechat(String openId){
        return null;
    }

    public ResultMsg loginForToken(String token){
        //通過token拿到用戶信息,然后再重新登陸了一次
        return  null;
    }

    public ResultMsg loginForTelphone(String telphone,String code){

        return null;
    }

    public ResultMsg loginForRegist(String username,String password){
        super.regist(username,null);
        return super.login(username,null);
    }
}

客戶端測(cè)試代碼:

public class SiginForThirdServiceTest {
    public static void main(String[] args) {
        SinginForThirdService service = new SinginForThirdService();
        service.login("tom","123456");
        service.loginForQQ("sdfasdfasf");
        service.loginForWechat("sdfasfsa");
    }
}

通過這么一個(gè)簡(jiǎn)單的適配,完成了代碼兼容。當(dāng)然,我們代碼還可以更加優(yōu)雅,根據(jù)不 同的登錄方式,創(chuàng)建不同的 Adapter。首先,創(chuàng)建 LoginAdapter 接口:

/**
 * 在適配器里面,這個(gè)接口是可有可無,不要跟模板模式混淆
 * 模板模式一定是抽象類,而這里僅僅只是一個(gè)接口
 */
public interface LoginAdapter {
    boolean support(Object adapter);
    ResultMsg login(String id,Object adapter);

}

分別實(shí)現(xiàn)不同的登錄適配,QQ 登錄 LoginForQQAdapter:

public class LoginForQQAdapter implements LoginAdapter {
    public boolean support(Object adapter) {
        return adapter instanceof LoginForQQAdapter;
    }

    public ResultMsg login(String id, Object adapter) {
        return null;
    }
}

新浪微博登錄 LoginForSinaAdapter:

public class LoginForSinaAdapter implements LoginAdapter {
    public boolean support(Object adapter) {
        return adapter instanceof LoginForSinaAdapter;
    }
    public ResultMsg login(String id, Object adapter) {
        return null;
    }
}

手機(jī)號(hào)登錄 LoginForTelAdapter:

public class LoginForTelAdapter implements LoginAdapter {
    public boolean support(Object adapter) {
        return adapter instanceof LoginForTelAdapter;
    }
    public ResultMsg login(String id, Object adapter) {
        return null;
    }
}
Token 自動(dòng)登錄 LoginForTokenAdapter:
```java
public class LoginForTokenAdapter implements LoginAdapter {
    public boolean support(Object adapter) {
        return adapter instanceof LoginForTokenAdapter;
    }
    public ResultMsg login(String id, Object adapter) {
        return null;
    }
}

微信登錄 LoginForWechatAdapter:

public class LoginForWechatAdapter implements LoginAdapter {
    public boolean support(Object adapter) {
        return adapter instanceof LoginForWechatAdapter;
    }
    public ResultMsg login(String id, Object adapter) {
        return null;
    }
}

然后,創(chuàng)建第三方登錄兼容接口 IPassportForThird:

public interface IPassportForThird {

    /**
     * QQ登錄
     * @param id
     * @return
     */
    ResultMsg loginForQQ(String id);

    /**
     * 微信登錄
     * @param id
     * @return
     */
    ResultMsg loginForWechat(String id);

    /**
     * 記住登錄狀態(tài)后自動(dòng)登錄
     * @param token
     * @return
     */
    ResultMsg loginForToken(String token);

    /**
     * 手機(jī)號(hào)登錄
     * @param telphone
     * @param code
     * @return
     */
    ResultMsg loginForTelphone(String telphone, String code);

    /**
     * 注冊(cè)后自動(dòng)登錄
     * @param username
     * @param passport
     * @return
     */
    ResultMsg loginForRegist(String username, String passport);
}

實(shí)現(xiàn)兼容 PassportForThirdAdapter:

/**
 * 結(jié)合策略模式、工廠模式、適配器模式
 */
public class PassportForThirdAdapter extends SiginService implements IPassportForThird {

    public ResultMsg loginForQQ(String id) {
//        return processLogin(id,RegistForQQAdapter.class);
        return processLogin(id,LoginForQQAdapter.class);
    }

    public ResultMsg loginForWechat(String id) {
        return processLogin(id,LoginForWechatAdapter.class);
    }

    public ResultMsg loginForToken(String token) {
        return processLogin(token,LoginForTokenAdapter.class);
    }

    public ResultMsg loginForTelphone(String telphone, String code) {
        return processLogin(telphone,LoginForTelAdapter.class);
    }

    public ResultMsg loginForRegist(String username, String passport) {
        super.regist(username,passport);
        return super.login(username,passport);
    }

    private ResultMsg processLogin(String key,Class<? extends LoginAdapter> clazz){
        try{
            //適配器不一定要實(shí)現(xiàn)接口
            LoginAdapter adapter = clazz.newInstance();

            //判斷傳過來的適配器是否能處理指定的邏輯
            if(adapter.support(adapter)){
                return adapter.login(key,adapter);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
    //類圖的快捷鍵  Ctrl + Alt + Shift + U
}

客戶端測(cè)試代碼:

public class PassportTest {

    public static void main(String[] args) {

        IPassportForThird passportForThird = new PassportForThirdAdapter();

        passportForThird.loginForQQ("");


    }

}

最后,來看一下類圖:



至此,我們?cè)谧裱_閉原則的前提下,完整地實(shí)現(xiàn)了一個(gè)兼容多平臺(tái)登錄的業(yè)務(wù)場(chǎng)景。 當(dāng)然,我目前的這個(gè)設(shè)計(jì)也并不完美,僅供參考,感興趣的小伙伴可以繼續(xù)完善這段代 碼。例如適配器中的參數(shù)目前是寫死為 String,改為 Object[]應(yīng)該更合理。 學(xué)習(xí)到這里,相信小伙伴會(huì)有一個(gè)疑問了:適配器模式跟策略模式好像區(qū)別不大?在這 里我要強(qiáng)調(diào)一下,適配器模式主要解決的是功能兼容問題,單場(chǎng)景適配大家可能不會(huì)和 策略模式有對(duì)比。但多場(chǎng)景適配大家產(chǎn)生聯(lián)想和混淆了。其實(shí),大家有沒有發(fā)現(xiàn)一個(gè)細(xì) 節(jié),我給每個(gè)適配器都加上了一個(gè) support()方法,用來判斷是否兼容,support()方法 的參數(shù)也是 Object 的,而 supoort()來自于接口。適配器的實(shí)現(xiàn)邏輯并不依賴于接口, 我們完全可以將 LoginAdapter 接口去掉。而加上接口,只是為了代碼規(guī)范。上面的代碼可以說是策略模式、簡(jiǎn)單工廠模式和適配器模式的綜合運(yùn)用。

適配器模式的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):
1、能提高類的透明性和復(fù)用,現(xiàn)有的類復(fù)用但不需要改變。
2、目標(biāo)類和適配器類解耦,提高程序的擴(kuò)展性。
3、在很多業(yè)務(wù)場(chǎng)景中符合開閉原則。
缺點(diǎn):
1、適配器編寫過程需要全面考慮,可能會(huì)增加系統(tǒng)的復(fù)雜性。
2、增加代碼閱讀難度,降低代碼可讀性,過多使用適配器會(huì)使系統(tǒng)代碼變得凌亂。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容