設計模式之裝飾器模式(java實現)
裝飾器模式(Decorator):結構型設計模式,為了實現類在不修改原始類的基礎上進行動態的覆蓋或者增加方法,該實現保持了跟原有類的層級關係。這種設計模式允許向一個現有的物件新增新的功能,同時又不改變其結構。算是一種非常特殊的介面卡模式。
在實際業務中,有時候我們會建立了多層子類,但如果當子類層數超過三層,一般來說不太建議,這個時候可以考慮使用裝飾器模式。
Spring中的應用場景:在我們的專案中遇到這樣一個問題:我們的專案需要連線多個數據庫,而且不同的客戶在每 次訪問中根據需要會去訪問不同的資料庫。我們以往在 Spring 和 Hibernate 框架中總是配置一個數據 源,因而 SessionFactory 的 DataSource 屬性總是指向這個資料來源並且恆定不變,所有 DAO 在使用SessionFactory 的時候都是通過這個資料來源訪問資料庫。但是現在,由於專案的需要,我們的 DAO 在 訪問 SessionFactory 的時候都不得不在多個數據源中不斷切換,問題就出現了:如何讓SessionFactory 在執行資料持久化的時候,根據客戶的需求能夠動態切換不同的資料來源?我們能不能 在 Spring 的框架下通過少量修改得到解決?是否有什麼設計模式可以利用呢?
首先想到在 Spring 的 ApplicationContext 中配置所有的 DataSource。這些 DataSource 可能是各 種不同型別的,比如不同的資料庫:Oracle、SQL Server、MySQL 等,也可能是不同的資料來源:比如Apache 提 供 的 org.apache.commons.dbcp.BasicDataSource 、 Spring 提 供 的org.springframework.jndi.JndiObjectFactoryBean 等。然後 SessionFactory 根據客戶的每次 請求,將 DataSource 屬性設定成不同的資料來源,以到達切換資料來源的目的。
由於裝飾器模式算是一種非常特殊的介面卡模式,所以,我這邊使用之前介面卡模式中的那個例子進行改造,使用裝飾器模式來實現。之前介面卡模式的java例子
依舊是新增登入類別,不過此時不同的是新增兩個介面ISignInService,ISignInForThirdService.
ISignInForThirdService是繼承於ISignInService的,SignInForThirdService不再是對SignInService的繼承,而是對ISignInForThirdService的實現。
類圖
具體實現:
User類:
package Decorator; public class User { private String username; private String password; private String mid; private String info; public User() { } 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; } }
ResultMsg:
package Decorator; 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; } }
ISignInService介面和SignInService類:
package Decorator;
public interface ISignInService {
/**
* 註冊介面
* @param username
* @param password
* @return
*/
public ResultMsg register(String username, String password);
/**
* 登入的介面
* @param username
* @param password
* @return
*/
public ResultMsg login(String username, String password);
}
package Decorator;
public class SignInService implements ISignInService {
/**
* 註冊方法
* @param username
* @param password
* @return
*/
public ResultMsg register(String username, String password){
return new ResultMsg(200,"註冊成功",new User());
}
/**
* 登入的方法
* @param username
* @param password
* @return
*/
public ResultMsg login(String username, String password){
System.out.println("登陸成功");
return null;
}
}
ISignInForThirdService介面和SignInForThirdService類:
package Decorator;
public interface ISignInForThirdService extends ISignInService {
public ResultMsg loginForQQ(String openId);
public ResultMsg loginForWechat(String openId);
public ResultMsg loginForToken(String token);
public ResultMsg loginForTelephone(String telephone, String code);
public ResultMsg loginForRegister(String username, String password);
public ResultMsg login(String username, String password);
}
package Decorator;
public class SignInForThirdService implements ISignInForThirdService {
private ISignInService service;
public SignInForThirdService(ISignInService service){
this.service = service;
}
@Override
public ResultMsg register(String username, String password) {
return service.register(username,password);
}
@Override
public ResultMsg login(String username, String password) {
return service.login(username,password);
}
public ResultMsg loginForQQ(String openId){
//1、openId是全域性唯一,我們可以把它當做是一個使用者名稱(加長)
//2、密碼預設為QQ_EMPTY
//3、註冊(在原有系統裡面建立一個使用者)
//4、呼叫原來的登入方法
String QQDefaultPasswords = "QQ_EMPTY";
//這裡省略查重驗證,預設為新使用者,實際專案執行會有
System.out.println("QQ登入");
return loginForRegister(openId,QQDefaultPasswords);
}
public ResultMsg loginForWechat(String openId){
String WechatDefaultPasswords = "WECHAT_EMPTY";
System.out.println("wechat登入");
return loginForRegister(openId,WechatDefaultPasswords);
}
public ResultMsg loginForToken(String token){
//通過token拿到使用者資訊,然後再重新登陸了一次
User user = new User();
System.out.println("token自動登入");
return login(user.getUsername(),user.getPassword());
}
public ResultMsg loginForTelephone(String telephone, String code){
String telephoneDefaultPasswords = "TELEPHONE_EMPTY";
System.out.println("手機號登入");
return loginForRegister(telephone,telephoneDefaultPasswords);
}
public ResultMsg loginForRegister(String username, String password){
this.register(username,password);
return this.login(username,password);
}
}
比較特殊的是實際的呼叫方式的實現SignInForThirdServiceTest:
package Decorator;
public class SignInForThirdServiceTest {
public static void main(String[] args) {
ISignInForThirdService iSignInForThirdService = new SignInForThirdService(new SignInService());
//原來的功能依舊對外開放,依舊保留
//新的功能同樣的也可以使用
iSignInForThirdService.loginForQQ("sdfgdgfwresdf9123sdf");
iSignInForThirdService.loginForTelephone("1560017471","sdha");
iSignInForThirdService.loginForToken("dsajdsakldjksafjhfkasljkla");
iSignInForThirdService.loginForWechat("dhafkahkjdsada");
}
}
使用SignInForThirdService來裝飾SignInService;
執行結果:
以上就是一個裝飾器模式的實現了。
可以看到裝飾器模式可以替代繼承,來實現功能。因此,但我們要實現一個類的功能擴充套件的時候,可以考慮裝飾器模式。
那麼裝飾器模式和介面卡模式的差別是哪些呢?
裝飾器模式 | 介面卡模式 |
是一種非常特別的介面卡模式 | 可以不保留層級關係 |
裝飾者和被裝飾者都要實現同一個介面, 主要目的是為了擴充套件,依舊保留OOP關係 |
適配者和被適配者沒有必然的層級聯絡 通常採用代理或者繼承形式進行包裝
|
滿足is-a的關係 | 滿足has-a關係 |
注重的是覆蓋、擴充套件 | 注重相容、轉換 |
在Spring 原始碼中,但類名裡面有 Wrapper,Decorator,基本上可以認為是裝飾器模式。