装饰者模式- 2020-12-07 00:13- 设计模式设计模式,装饰者模式


装饰者模式(Decorator Pattern)是指在不改变原有对象的基础之上,将功能附加到对 象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式。

适用场景

1、用于扩展一个类的功能或给一个类添加附加职责。
2、动态的给一个对象添加功能,这些功能可以再动态的撤销。

优点

1、装饰者是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象
扩展功能,即插即用。
2、通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。
3、装饰者完全遵守开闭原则。

缺点

1、会出现更多的代码,更多的类,增加程序复杂性。
2、动态装饰时,多层装饰时会更复杂。

demo

煎饼原生场景

来看一个这样的场景,上班族白领其实大多有睡懒觉的习惯,每天早上上班都是踩点, 于是很多小伙伴为了多赖一会儿床都不吃早餐。那么,也有些小伙伴可能在上班路上碰 到卖煎饼的路边摊,都会顺带一个到公司茶水间吃早餐。卖煎饼的大姐可以给你的煎饼加鸡蛋,也可以加香肠。
首先创建一个基础套餐煎饼 Battercake 类

  1. /**
  2. * 基础煎饼套餐
  3. *
  4. * @author Bai
  5. * @date 2020/12/5 23:32
  6. */
  7. public class BaseBattercake {
  8. public String cook() {
  9. return "基础套餐:一个鸡蛋";
  10. }
  11. public int getPrice() {
  12. return 5;
  13. }
  14. }

创建一个加鸡蛋的煎饼 EggBattercake类

  1. /**
  2. * 再加一个蛋煎饼
  3. *
  4. * @author Bai
  5. * @date 2020/12/6 0:24
  6. */
  7. public class EggBattercake extends BaseBattercake {
  8. @Override
  9. public String cook() {
  10. return super.cook() + "再加一个鸡蛋";
  11. }
  12. @Override
  13. public int getPrice() {
  14. return super.getPrice() + 1;
  15. }
  16. }

再创建一个既加鸡蛋又加香肠的 SausageBattercake类

  1. /**
  2. * 煎饼加香肠
  3. *
  4. * @author Bai
  5. * @date 2020/12/6 0:25
  6. */
  7. public class SausageBattercake extends BaseBattercake {
  8. @Override
  9. public String cook() {
  10. return super.cook() + "再加一根香肠";
  11. }
  12. @Override
  13. public int getPrice() {
  14. return super.getPrice() + 2;
  15. }
  16. }

  1. @Test
  2. public void demo() {
  3. /**
  4. * 初始化一个基础套餐,原有套餐是不变的(不改变被装饰者原有逻辑,只是做增强&扩展)
  5. */
  6. Battercake battercake = new BaseBattercake();
  7. EggBattercake eggBattercake = new EggBattercake();
  8. SausageBattercake sausageBattercake = new SausageBattercake();
  9. System.out.println(battercake.cook() + " 价格:" + battercake.getPrice());
  10. System.out.println(eggBattercake.cook() + " 价格:" + eggBattercake.getPrice());
  11. System.out.println(sausageBattercake.cook() + " 价格:" + sausageBattercake.getPrice());
  12. }
  1. 基础套餐:一个鸡蛋 价格:5
  2. 基础套餐:一个鸡蛋再加一个鸡蛋 价格:6
  3. 基础套餐:一个鸡蛋再加一根香肠 价格:7

运行结果没有问题。但是,如果用户需要一个加 2 个鸡蛋加 1 根香肠的煎饼,那么用我 们现在的类结构是创建不出来的,也无法自动计算出价格,除非再创建一个类做定制。 如果需求再变,一直加定制显然是不科学的。那么下面我们就用装饰者模式来解决上面的问题。

煎饼+装饰者模式

先创建一个建煎饼的抽象 Battercake类

  1. /**
  2. * 煎饼 因煎饼经常变换,所以抽取出来
  3. *
  4. * @author Bai
  5. * @date 2020/12/5 23:31
  6. */
  7. public interface Battercake {
  8. /**
  9. * 摊煎饼
  10. *
  11. * @return
  12. */
  13. String cook();
  14. /**
  15. * 价格
  16. *
  17. * @return
  18. */
  19. int getPrice();
  20. }

创建一个基本的煎饼(或者叫基础套餐)BaseBattercake,也就是被装饰者

  1. /**
  2. * 基础煎饼套餐
  3. *
  4. * @author Bai
  5. * @date 2020/12/5 23:32
  6. */
  7. public class BaseBattercake implements Battercake {
  8. @Override
  9. public String cook() {
  10. return "基础套餐:一个鸡蛋";
  11. }
  12. @Override
  13. public int getPrice() {
  14. return 5;
  15. }
  16. }

然后,再创建一个扩展套餐的抽象装饰者 BattercakeDecorator 类

  1. /**
  2. * 煎饼装饰器, 继承被装饰接口
  3. *
  4. * @author Bai
  5. * @date 2020/12/5 23:34
  6. */
  7. public interface BattercakeDecorator extends Battercake {
  8. }

然后,创建鸡蛋装饰者 EggDecorator 类

  1. /**
  2. * 鸡蛋 装饰
  3. *
  4. * @author Bai
  5. * @date 2020/12/5 23:38
  6. */
  7. public class EggDecorator implements BattercakeDecorator {
  8. /**
  9. * 被装饰者引用
  10. */
  11. private Battercake battercake;
  12. public EggDecorator(Battercake battercake) {
  13. this.battercake = battercake;
  14. }
  15. @Override
  16. public String cook() {
  17. return battercake.cook() + ",再加一个鸡蛋";
  18. }
  19. @Override
  20. public int getPrice() {
  21. return battercake.getPrice() + 1;
  22. }
  23. }

创建香肠装饰者 SausageDecorator类

  1. /**
  2. * 香肠装饰者
  3. *
  4. * @author Bai
  5. * @date 2020/12/5 23:42
  6. */
  7. public class SausageDecorator implements BattercakeDecorator {
  8. /**
  9. * 持有被装饰者的引用
  10. */
  11. private Battercake battercake;
  12. public SausageDecorator(Battercake battercake) {
  13. this.battercake = battercake;
  14. }
  15. @Override
  16. public String cook() {
  17. return battercake.cook() + ",再加一根香肠";
  18. }
  19. @Override
  20. public int getPrice() {
  21. return battercake.getPrice() + 2;
  22. }
  23. }

测试

  1. @Test
  2. public void demo() {
  3. /**
  4. * 初始化一个基础套餐,原有套餐是不变的(不改变被装饰者原有逻辑,只是做增强&扩展)
  5. */
  6. Battercake battercake = new BaseBattercake();
  7. //加一个鸡蛋
  8. battercake = new EggDecorator(battercake);
  9. //再加一个香肠
  10. battercake = new SausageDecorator(battercake);
  11. System.out.println(battercake.cook());
  12. System.out.println(battercake.getPrice());
  13. }
  1. 基础套餐:一个鸡蛋,再加一个鸡蛋,再加一根香肠
  2. 8

使用了装饰者模式之后,对原有逻辑做了增强,原有逻辑不做变动,如果煎饼中还需要加入生菜,则需要再增加一个生菜装饰者即可,LettuceDecorator

  1. /**
  2. * 生菜装饰
  3. *
  4. * @author Bai
  5. * @date 2020/12/5 23:56
  6. */
  7. public class LettuceDecorator implements BattercakeDecorator {
  8. /**
  9. * 持有被装饰者者
  10. */
  11. private Battercake battercake;
  12. public LettuceDecorator(Battercake battercake) {
  13. this.battercake = battercake;
  14. }
  15. @Override
  16. public String cook() {
  17. return battercake.cook();
  18. }
  19. @Override
  20. public int getPrice() {
  21. return battercake.getPrice();
  22. }
  23. }
  1. @Test
  2. public void demo() {
  3. /**
  4. * 初始化一个基础套餐,原有套餐是不变的(不改变被装饰者原有逻辑,只是做增强&扩展)
  5. */
  6. Battercake battercake = new BaseBattercake();
  7. //加一个鸡蛋
  8. battercake = new EggDecorator(battercake);
  9. //再加一个香肠
  10. battercake = new SausageDecorator(battercake);
  11. //再加免费生菜
  12. battercake = new LettuceDecorator(battercake);
  13. System.out.println(battercake.cook());
  14. System.out.println(battercake.getPrice());
  15. }

用户登录 重构

原有登录方式为 用户名+密码,现在需要增加第三方登录方式,微信 qq 手机号,在不改动原有逻辑的情况下,遵循开闭原则完成此需求。
原有登录服务LoginService

  1. /**
  2. * 原有登录方式:根据用户名登录
  3. *
  4. * @author Bai
  5. * @date 2020/12/5 23:59
  6. */
  7. public interface LoginService {
  8. /**
  9. * 根据用户名登录
  10. *
  11. * @return
  12. */
  13. boolean loginByName();
  14. }

登录服务实现

  1. /**
  2. * 模拟用户根据用户名登录
  3. *
  4. * @author Bai
  5. * @date 2020/12/5 16:23
  6. */
  7. public class LoginServiceImpl implements LoginService {
  8. @Override
  9. public boolean loginByName() {
  10. System.out.println("根据用户名登录");
  11. return true;
  12. }
  13. }

增加装饰者LoginDecoratorService,继承LoginService,持有被装饰对象的所有方法

  1. /**
  2. * 装饰者类
  3. *
  4. * @author Bai
  5. * @date 2020/12/6 0:05
  6. */
  7. public interface LoginDecoratorService extends LoginService {
  8. /**
  9. * QQ登录
  10. *
  11. * @return
  12. */
  13. boolean qqLogin();
  14. /**
  15. * 微信登录
  16. *
  17. * @return
  18. */
  19. boolean weixinLogin();
  20. /**
  21. * 手机号登录
  22. *
  23. * @return
  24. */
  25. boolean mobileLogin();
  26. }

装饰者实现

  1. /**
  2. * 装饰者实现
  3. *
  4. * @author Bai
  5. * @date 2020/12/6 0:08
  6. */
  7. public class LoginDecoratorServiceImpl implements LoginDecoratorService {
  8. /**
  9. * 持有被装饰者引用
  10. */
  11. private LoginService loginService;
  12. public LoginDecoratorServiceImpl(LoginService loginService) {
  13. this.loginService = loginService;
  14. }
  15. @Override
  16. public boolean qqLogin() {
  17. System.out.println("QQ登录");
  18. return true;
  19. }
  20. @Override
  21. public boolean weixinLogin() {
  22. System.out.println("微信登录");
  23. return true;
  24. }
  25. @Override
  26. public boolean mobileLogin() {
  27. System.out.println("手机号登录");
  28. return true;
  29. }
  30. /**
  31. * 使用被装饰者原有逻辑
  32. */
  33. @Override
  34. public boolean loginByName() {
  35. return loginService.loginByName();
  36. }
  37. }

测试

  1. @Test
  2. public void loginTest() {
  3. //原有登录方式:根据用户名登录,不改变原有逻辑上增加 开闭原则
  4. LoginService loginService = new LoginServiceImpl();
  5. //新增第三方登录方式
  6. LoginDecoratorService decoratorService = new LoginDecoratorServiceImpl(loginService);
  7. decoratorService.loginByName();
  8. decoratorService.qqLogin();
  9. }

装饰者模式最本质的特征是讲原有类的附加功能抽离出来,简化原有类的逻辑。通过这 样两个案例,我们可以总结出来,其实抽象的装饰者是可有可无的,具体可以根据业务 模型来选择。

源码TODO

装饰器模式在源码中也应用得非常多,在 JDK 中体现最明显的类就是 IO 相关的类,如 BufferedReader、InputStream、OutputStream

参考资料

咕泡VIP架构师课程