设计模式之装饰器模式(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,基本上可以认为是装饰器模式。