设计模式(六)之装饰器模式(Decorator Pattern)深入浅出
内容定位:有重构项目需求的人群一定要掌握装饰者模式。
装饰者模式的定义:
- 装饰者模式(Decorator Pattern)是指在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能)。
- 属于结构型模式
装饰者模式的适用场景:
- 用于扩展一个类的功能或给一个类添加附加职责。
- 动态的给一个对象添加功能,这些功能可以再动态的撤销。
装饰者模式源码案例:
- JDK源码中BuffteredReader、LineNumberReader装饰Reader抽象类;FileInputStream、FilterInputStream、ByteArrayInputStream、DataInputStream、BufferedInputStream装饰的是InputStream接口,实现可扩展
- Spring源码中TransactionAwareCacheDecorator装饰Cache接口,主要用来处理事务缓存
- SpringMvc源码中HttpHeadResponseDecorator装饰ServerHttpResponse接口,这个接口负责委派
去父类中查看
- Mybatis源码中对org.apache.ibatis.cache.Cache接口进行包装
包装类有:
生活场景案例
案例:我们在买煎饼时需要添加鸡蛋、香肠、菜....等等
(一)首先不用装饰者实现,通过继承实现一下
煎饼类
/** * @author: ZhouCong * @date: Create in 2021/1/12 17:18 * @description: 煎饼类 */ public class Battercake { public String getMsg(){ return "煎饼"; } public int price(){ return 5; } }
煎饼加一个鸡蛋
/** * @author: ZhouCong * @date: Create in 2021/1/12 17:20 * @description: 煎饼加一个鸡蛋 */ public class BattercakeWithEgg extends Battercake { @Override public String getMsg() { return super.getMsg() + " +1个鸡蛋"; } /** * 加一个鸡蛋加2元 */ @Override public int price() { return super.price() + 2; } }
煎饼加一个香肠
/** * @author: ZhouCong * @date: Create in 2021/1/12 17:22 * @description: */ public class BattercakeWithEggAndSausage extends BattercakeWithEgg { @Override public String getMsg() { return super.getMsg() + " +1个香肠"; } /** * 1个香肠价格加2元 */ @Override public int price() { return super.price() + 2; } }
测试
/** * @author: ZhouCong * @date: Create in 2021/1/12 17:26 * @description: */ public class BattercakeTest { public static void main(String[] args) { Battercake battercake = new Battercake(); System.out.println(battercake.getMsg() + ",总价格:" + battercake.price()); Battercake battercakeWithEgg = new BattercakeWithEgg(); System.out.println(battercakeWithEgg.getMsg() + ",总价格:" + battercakeWithEgg.price()); Battercake battercakeWithEggAndSausage = new BattercakeWithEggAndSausage(); System.out.println(battercakeWithEggAndSausage.getMsg() + ",总价格:" + battercakeWithEggAndSausage.price()); } }
类结构图
我们会发现,如果需求频繁变动,比如加两个鸡蛋就需要不停的创建类,然后继承煎饼类。
(二)下面用装饰者模式实现
煎饼抽象类
/** * @author: ZhouCong * @date: Create in 2021/1/12 17:44 * @description: 煎饼抽象类 */ public abstract class Battercake { protected abstract String getMsg(); protected abstract int price(); }
基础煎饼类
/** * @author: ZhouCong * @date: Create in 2021/1/12 17:45 * @description: 基础煎饼(什么也不加) */ public class BaseBattercake extends Battercake { @Override public String getMsg(){ return "煎饼"; } @Override public int price(){ return 5; } }
装饰器抽象类(可有可无)
/** * @author: ZhouCong * @date: Create in 2021/1/12 17:46 * @description: 包装抽象类,继承煎饼抽象类,再怎么包装也是煎饼 */ public abstract class BattercakeDecorator extends Battercake { // 静态代理,委派模式:将修改的权力交给了装饰器 private Battercake battercake; public BattercakeDecorator(Battercake battercake) { this.battercake = battercake; } @Override protected String getMsg() { return this.battercake.getMsg(); } @Override protected int price() { return this.battercake.price(); } }
加鸡蛋的装饰器
/** * @author: ZhouCong * @date: Create in 2021/1/12 17:50 * @description: 鸡蛋包装类 */ public class EggDecorator extends BattercakeDecorator { public EggDecorator(Battercake battercake) { super(battercake); } @Override protected String getMsg() { return super.getMsg() + " +1个鸡蛋"; } @Override protected int price() { return super.price() + 2; } }
加香肠的装饰器
/** * @author: ZhouCong * @date: Create in 2021/1/12 17:52 * @description: */ public class SausageDecorator extends BattercakeDecorator { public SausageDecorator(Battercake battercake) { super(battercake); } @Override public String getMsg() { return super.getMsg() + " +1个香肠"; } /** * 1个香肠价格加2元 */ @Override public int price() { return super.price() + 2; } }
测试用例
/** * @author: ZhouCong * @date: Create in 2021/1/12 17:54 * @description: */ public class BattercakeTest { public static void main(String[] args) { Battercake battercake; // 路边买一个煎饼 battercake = new BaseBattercake(); // 煎饼有点小,再想加个鸡蛋 battercake = new EggDecorator(battercake); // 再加一个鸡蛋 battercake = new EggDecorator(battercake); // 再加一跟香肠 battercake = new SausageDecorator(battercake); System.out.println(battercake.getMsg() + "总价:" + battercake.price()); } }
运行结果
类结构图
对比继承可以看出,将添加的行为交给了装饰器去做,有点类似静态代理和委派模式
简单来说:装饰器模式就是通过构造器入参,代替继承实现可扩展的一种方式
业务场景案例
案例:系统需要在原有的登录方式上,添加QQ、微信等多种登录方式(这里拿适配器模式讲解的业务场景案例: 连接地址)
登录注册接口
public interface ISiginService { /** * 注册方法 */ ResultMsg regist(String username,String password); /** * 登录方法 */ ResultMsg login(String username,String password); }
返回对象类
/** * @author: ZhouCong * @date: Create in 2021/1/12 11:55 * @description: */ public class ResultMsg { private int code; private String msg; private String data; public ResultMsg(int code, String msg, String data) { this.code = code; this.msg = msg; this.data = data; } }
登录注册实现类
/** * @author: ZhouCong * @date: Create in 2021/1/12 18:40 * @description: 登录注册实现 */ public class SiginService implements ISiginService { /** * 注册方法 */ @Override public ResultMsg regist(String username,String password){ return new ResultMsg(200,"注册成功",null); } /** * 登录方法 */ @Override public ResultMsg login(String username,String password){ return new ResultMsg(200,"登录成功",null); } }
扩展三方登录的接口
/** * @author: ZhouCong * @date: Create in 2021/1/12 18:43 * @description: 三方登录接口 */ public interface ISiginForThirdService extends ISiginService { /** * QQ登录 */ ResultMsg loginForQQ(String openId); /** * 微信登录 */ ResultMsg loginForWechat(String openId); /** * 新浪登录 */ ResultMsg loginForSina(String openId); /** * token登录 */ ResultMsg loginForToken(String token); /** * 手机验证登录 */ ResultMsg loginForTelphone(String telphone,String code); /** * 注册账号后自动登录 */ ResultMsg loginForRegister(String username,String passport); }
三方登录包装器
/** * @author: ZhouCong * @date: Create in 2021/1/12 18:46 * @description: 三方登录服务 包装器 */ public class SiginForThirdService implements ISiginForThirdService { private ISiginService siginService; public SiginForThirdService(ISiginService siginService) { this.siginService = siginService; } @Override public ResultMsg loginForQQ(String openId) { return null; } @Override public ResultMsg loginForWechat(String openId) { return null; } @Override public ResultMsg loginForSina(String openId) { return null; } @Override public ResultMsg loginForToken(String token) { return null; } @Override public ResultMsg loginForTelphone(String telphone, String code) { return null; } @Override public ResultMsg loginForRegister(String username, String passport) { return null; } @Override public ResultMsg regist(String username, String password) { return this.siginService.regist(username,password); } @Override public ResultMsg login(String username, String password) { return this.siginService.login(username,password); } }
测试用例
/** * @author: ZhouCong * @date: Create in 2021/1/13 10:34 * @description: */ public class DecoratorTest { public static void main(String[] args) { SiginForThirdService siginForThirdService = new SiginForThirdService(new SiginService()); siginForThirdService.loginForQQ("fdfdfasd"); } }
类结构图
总结
模式对比:
装饰器模式 | 适配器模式 | |
形式 | 是一种非常特别的适配器模式 | 没有层级关系,装饰器模式有层级关系 |
定义 | 装饰者和被装饰者都实现同一个接口,主要目的是为了扩展之后依旧保留OOP关系 | 适配者和被适配者没有必然的联系,通常是采用继承或代理的形式进行包装 |
关系 | 满足is-a关系 | 满足has-a关系 |
功能 | 注重覆盖、扩展 | 注重兼容、转换 |
设计 | 前置考虑,架构设计之初考虑 | 后置考虑,亡羊补牢 |
装饰器模式的优点:
- 装饰者是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象扩展功能,即插即用。
- 通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。
- 装饰者完全遵守开闭原则。
装饰器模式的缺点:
- 会出现更多的代码,更多的类,增加程序复杂性。
- 动态装饰时,多层装饰时会更复杂。
以上对装饰器模式的介绍到此结束,欢迎批评指正。 附:源码地址