如何快速搞定第三方登錄且易擴展?


本文節選自《設計模式就該這樣學》

1 使用類適配器重構第三方登錄自由適配

我們使用適配模式來實現一個實際的業務場景,解決實際問題。年紀稍微大一點的小伙伴一定經歷過這樣的過程。很早以前開發的老系統應該都有登錄接口,但是隨着業務的發展和社會的進步,單純地依賴用戶名密碼登錄顯然不能滿足用戶需求。現在,大部分系統都已經支持多種登錄方式,如QQ登錄、微信登錄、手機登錄、微博登錄等,同時保留用戶名密碼的登錄方式。雖然登錄形式豐富,但是登錄后的處理邏輯可以不必改,都是將登錄狀態保存到Session,遵循開閉原則。首先創建統一的返回結果ResultMsg類。


/**
 * Created by Tom.
 */
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;
    }
}

假設在老系統中,處理登錄邏輯的代碼在PassportService類中。


public class PassportService {

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


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

}

為了遵循開閉原則,不修改老系統的代碼。下面開啟代碼重構之路,創建Member類。


/**
 * Created by Tom.
 */
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;
    }
}

我們也不改動運行非常穩定的代碼,創建Target角色IPassportForThird接口。


public interface IPassportForThird {

    ResultMsg loginForQQ(String openId);

    ResultMsg loginForWechat(String openId);

    ResultMsg loginForToken(String token);

    ResultMsg loginForTelphone(String phone,String code);

}

創建Adapter角色實現兼容,創建一個新的類PassportForThirdAdapter,繼承原來的邏輯。


public class PassportForThirdAdapter extends PassportService implements IPassportForThird {

    public ResultMsg loginForQQ(String openId) {
        return loginForRegist(openId,null);
    }

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

    public ResultMsg loginForToken(String token) {
        return loginForRegist(token,null);
    }

    public ResultMsg loginForTelphone(String phone, String code) {
        return loginForRegist(phone,null);
    }

    private ResultMsg loginForRegist(String username,String password){
        if(null == password){
            password = "THIRD_EMPTY";
        }
        super.regist(username,password);
        return super.login(username,password);
    }
}

客戶端測試代碼如下。


public static void main(String[] args) {
        PassportForThirdAdapter adapter = new PassportForThirdAdapter();
        adapter.login("tom","123456");
        adapter.loginForQQ("sjooguwoersdfjhasjfsa");
        adapter.loginForWechat("slfsjoljsdo8234ssdfs");
}

2 使用接口適配器優化代碼

通過這么一個簡單的適配動作,我們完成了代碼兼容。當然,代碼還可以更加優雅,根據不同的登錄方式,創建不同的Adapter。首先創建LoginAdapter接口。


public interface ILoginAdapter {
    boolean support(Object object);
    ResultMsg login(String id,Object adapter);
}

然后創建一個抽象類AbstractAdapter繼承PassportService原有的功能,同時實現ILoginAdapter接口,再分別實現不同的登錄適配,QQ登錄LoginForQQAdapter。


public class LoginForQQAdapter extends AbstractAdapter{
    public boolean support(Object adapter) {
        return adapter instanceof LoginForQQAdapter;
    }

    public ResultMsg login(String id, Object adapter) {
        if(!support(adapter)){return null;}
        //accesseToken
        //time
        return super.loginForRegist(id,null);

    }

}

手機登錄LoginForTelAdapter。


public class LoginForTelAdapter extends AbstractAdapter{
    public boolean support(Object adapter) {
        return adapter instanceof LoginForTelAdapter;
    }

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

Token自動登錄LoginForTokenAdapter。


public class LoginForTokenAdapter extends AbstractAdapter {
    public boolean support(Object adapter) {
        return adapter instanceof LoginForTokenAdapter;
    }

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

微信登錄LoginForWechatAdapter。


public class LoginForWechatAdapter extends AbstractAdapter{
    public boolean support(Object adapter) {
        return adapter instanceof LoginForWechatAdapter;
    }

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

接着創建適配器PassportForThirdAdapter類,實現目標接口IPassportForThird完成兼容。


public class PassportForThirdAdapter implements IPassportForThird {

    public ResultMsg loginForQQ(String openId) {
        return processLogin(openId, LoginForQQAdapter.class);
    }

    public ResultMsg loginForWechat(String openId) {

        return processLogin(openId, LoginForWechatAdapter.class);

    }

    public ResultMsg loginForToken(String token) {

        return processLogin(token, LoginForTokenAdapter.class);
    }

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


    private ResultMsg processLogin(String id,Class<? extends ILoginAdapter> clazz){
        try {
            ILoginAdapter adapter = clazz.newInstance();
            if (adapter.support(adapter)){
                return adapter.login(id,adapter);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
}

客戶端測試代碼如下。


public static void main(String[] args) {
        IPassportForThird adapter = new PassportForThirdAdapter();
        adapter.loginForQQ("sdfasdfasfasfas");
}

最后來看如下圖所示的類圖。

file

至此,在遵循開閉原則的前提下,我們完整地實現了一個兼容多平台登錄的業務場景。當然,目前的這個設計並不完美,僅供參考,感興趣的小伙伴們可以繼續完善這段代碼。例如適配器類中的參數目前是設置為String,改為Object[]應該更合理。

學習到這里,相信小伙伴們會有一個疑問:適配器模式與策略模式好像區別不大?我要強調一下,適配器模式主要解決的是功能兼容問題,單場景適配可能不會和策略模式有對比。但復雜場景適配大家就很容易混淆。其實,大家有沒有發現一個細節,筆者給每個適配器類都加上了一個support()方法,用來判斷是否兼容,support()方法的參數類型也是Object,而support()來自接口。適配器類的實現邏輯並不依賴接口,完全可以將ILoginAdapter接口去掉。而加上接口,只是為了代碼規范。上面代碼可以說是策略模式、簡單工廠模式和適配器模式的綜合運用。

關注微信公眾號『 Tom彈架構 』回復“設計模式”可獲取完整源碼。

【推薦】Tom彈架構:30個設計模式真實案例(附源碼),挑戰年薪60W不是夢

本文為“Tom彈架構”原創,轉載請注明出處。技術在於分享,我分享我快樂!
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力。關注微信公眾號『 Tom彈架構 』可獲取更多技術干貨!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM