装饰器模式(Decorator)
概述
为了某个实现类在不修改原始类的基础上进行动态地覆盖或者增加方法,该实现保持跟原有类的层级关系。装饰器模式实际上是一种非常特殊的适配器模式,必须同宗同源(通过继承来实现)。
装饰器和被装饰器都实现同一个接口,主要目的是为了扩展之后依旧保留OOP关系(同宗同源)。
穷举法
IO流包装、数据源包装、简历包装
应用场景
登陆方式扩展
还是上一章适配器模式的场景,需要扩展用户的登陆方式。如果采用装饰器模式,如下代码:
原登陆接口类
public interface ISigninService {
//注册
public ResultMsg regist(String username,String password);
//登陆
public ResultMsg login(String username,String password);
}
原登陆接口实现类
public class SigninService implements ISigninService {
public ResultMsg regist(String username,String password){
return new ResultMsg(200,"注册成功",new Member());
}
public ResultMsg login(String username,String password){
return null;
}
}
扩展登陆接口,继承了原来的登陆接口
public interface ISigninForThirdService extends ISigninService {
public ResultMsg loginForQQ(String openId);
public ResultMsg loginForWechat(String openId);
public ResultMsg loginForToken(String token);
public ResultMsg loginForTelphone(String telphone,String code);
public ResultMsg loginForRegist(String username,String password);
}
扩展登陆实现类,构造方法中注入了原登陆类
public class SigninForThirdService implements ISigninForThirdService {
private ISigninService service;
public SigninForThirdService(ISigninService service){
this.service = service;
}
@Override
public ResultMsg regist(String username, String password) {
return service.regist(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、调用原来的登录方法
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){
this.regist(username,null);
return this.login(username,null);
}
}
测试类
public class SigginTest {
public static void main(String[] args) {
ISigninForThirdService signinForThirdService = new SigninForThirdService(new SigninService());
signinForThirdService.loginForQQ("xxssdsd");
}
}
上述代码的优点显而易见——原来的功能依旧对外开放,依旧保留;新的功能同样的也可以使用。
事实上,若采用适配器模式对登陆类进行扩展,一般会在原有类中前加上@Deprecate注解,声明原有类不再建议使用。
spring中的装饰器模式
在项目中我们经常会需要链接多个数据库。DAO访问SessionFactory的时候不得不在多个数据源中不同切换,问题就出现了:如何让SessionFactory在执行数据持久化的时候,根据客户的需求能够动态切换不同的数据源?我们能不能在Spring的框架下通过少量修改得到解决?是否有什么设计模式可以利用?
- 首先要在Spring的ApplicationContext中配置所有的DataSource。
- 然后DataSource类继承数据库Connection并进行扩展,这里用到的就是装饰器模式。
- 采用策略模式根据用户的需要动态切换数据库
装饰器模式与适配器模式区别
装饰器模式 | 适配器模式 |
---|---|
是一种特殊的适配器模式(保留层级关系) | 可以不保留层级关系 |
装饰者和被装饰者都要实现同一个接口 | 适配者和被适配者没有必然的层级联系 |
主要目的是为了扩展,依旧保留OOP关系 | 通常采用代理或者继承形式进行包装 |
满足is-a的关系(是不是) | 满足has-a(有没有) |
注重的是覆盖、扩展 | 注重兼容、转换 |