—-慢慢来比较快,虚心学技术—-
**

概念

在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

允许向一个现有的对象添加新的功能,同时又不改变其结构

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

角色:

抽象构件(Component)角色:定义一个抽象接口用于规范核心对象。

具体构件(Concrete Component)角色核心对象,实现抽象构件,被装饰的角色。

抽象装饰(Decorator)角色继承抽象构件并包含具体构件的实例,通过子类对具体构件进行功能扩展。

具体装饰(ConcreteDecorator)角色实现抽象装饰的相关方法,对具体构件进行功能扩展。

推演

需求**:手抓饼(HandPanCake)加上不同的配料:如鸡柳(Chicken)、培根(Baconic)、火腿肠(Ham sausage)等**

首先创建一个手抓饼标准抽象

  1. /**
  2. * 手抓饼接口
  3. */
  4. abstract class HandPanCake{
  5. /**
  6. * 获取描述
  7. * @return
  8. */
  9. abstract String getName();
  10. /**
  11. * 获取价格
  12. * @return
  13. */
  14. abstract double getCost();
  15. }

创建实例实现上述方法,也就是具体的手抓饼类型

  1. /**
  2. * 原味手抓饼
  3. */
  4. class OriginalCake extend HandPanCake{
  5. @Override
  6. public String getName() {
  7. return "原味手抓饼";
  8. }
  9. @Override
  10. public double getCost() {
  11. return 5;
  12. }
  13. }

点一个原味手抓饼

  1. HandPanCake oc = new OriginalCake();
  2. System.out.println(oc.getName()+" "+oc.getCost());

结果:

  1. 原味手抓饼 5.0

这时候想要获取到不同搭配的手抓饼怎么办呢?

反例1(传统套路1)

手抓饼是核心不变的,不同的是配料(鸡柳,培根,火腿肠),我们可以创建新的子类中加入配料

  1. /**
  2. * 原味手抓饼+鸡柳
  3. */
  4. class WithChickenCake extend HandPanCake{
  5. @Override
  6. public String getName() {
  7. return "原味手抓饼 鸡柳";
  8. }
  9. @Override
  10. public double getCost() {
  11. return 5+4;
  12. }
  13. }
  14. /**
  15. * 原味手抓饼+鸡柳
  16. */
  17. class WithHamSausageCake extend HandPanCake{
  18. @Override
  19. public String getName() {
  20. return "原味手抓饼 火腿肠";
  21. }
  22. @Override
  23. public double getCost() {
  24. return 5+2;
  25. }
  26. }
  27. /**
  28. * 原味手抓饼+鸡柳+火腿肠
  29. */
  30. class ChickenWithHamSausageCake extend HandPanCake{
  31. @Override
  32. public String getName() {
  33. return "原味手抓饼 鸡柳 火腿肠";
  34. }
  35. @Override
  36. public double getCost() {
  37. return 5+4+2;
  38. }
  39. }

明显上述方法很不合适,因为变化无穷而产生的组合无穷,极易引起类爆炸

反例2(传统套路2)

在父类中直接包含配料即可,有客户端选择是否添加,假设可以更改父类代码,则将手抓饼标准更改如下:

  1. abstract class HandPanCake{
  2. //配料
  3. boolean addChicken,addBaconic,addHamSausage;
  4. /**
  5. * 获取描述
  6. * @return
  7. */
  8. abstract String getName();
  9. /**
  10. * 获取价格
  11. * @return
  12. */
  13. abstract double getCost();
  14. /**
  15. * 是否添加鸡柳
  16. * @param bool
  17. */
  18. void setAddChicken(boolean bool){
  19. this.addChicken = bool;
  20. }
  21. /**
  22. * 是否添加培根
  23. * @param bool
  24. */
  25. void setAddBaconic(boolean bool){
  26. this.addBaconic = bool;
  27. }
  28. /**
  29. * 是否添加火腿肠
  30. * @param bool
  31. */
  32. void setAddHamSausage(boolean bool){
  33. this.addHamSausage = bool;
  34. }
  35. }

手抓饼具体实现:

  1. class MyHandPanCake extends HandPanCake{
  2. @Override
  3. public String getName() {
  4. String name = "原味手抓饼";
  5. if(addChicken){
  6. name = name +" 鸡柳";
  7. }
  8. if(addBaconic){
  9. name = name +" 培根";
  10. }
  11. if(addHamSausage){
  12. name = name +" 火腿肠";
  13. }
  14. return name;
  15. }
  16. @Override
  17. public double getCost() {
  18. double sum = 5;//原价
  19. if(addChicken){
  20. sum = sum+4;
  21. }
  22. if(addBaconic){
  23. sum= sum+3;
  24. }
  25. if(addHamSausage){
  26. sum= sum+2;
  27. }
  28. return sum;
  29. }
  30. }

客户点手抓饼:

  1. HandPanCake mhpc = new MyHandPanCake();
  2. mhpc.setAddBaconic(true);// 加培根
  3. System.out.println(mhpc.getName()+" "+mhpc.getCost());
  4. mhpc.setAddChicken(true); // 加鸡柳
  5. System.out.println(mhpc.getName()+" "+mhpc.getCost());

结果如下:

  1. 原味手抓饼 培根 8.0
  2. 原味手抓饼 鸡柳 培根 12.0

类的数量是变少了,但是封装过甚,如果加了配料,那么必须修改源码加上去,违反了开闭原则

正例

保证抽象构件和具体构件稳定

  1. abstract class HandPanCake{
  2. /**
  3. * 获取描述
  4. * @return
  5. */
  6. abstract String getName();
  7. /**
  8. * 获取价格
  9. * @return
  10. */
  11. abstract double getCost();
  12. }
  13. /**
  14. * 原味手抓饼
  15. */
  16. class MyHandPanCake extends HandPanCake {
  17. @Override
  18. public String getName() {
  19. return "原味手抓饼";
  20. }
  21. @Override
  22. public double getCost() {
  23. return 5;
  24. }
  25. }

增加抽象装饰,实现抽象构件接口,并关联抽象构建:

  1. /**
  2. * 装饰器抽象 - 配料
  3. */
  4. abstract class Batching extends HandPanCake{
  5. private HandPanCake handPanCake;
  6. public Batching(HandPanCake handPanCake){
  7. this.handPanCake = handPanCake;
  8. }
  9. @Override
  10. public String getName() {
  11. return this.handPanCake.getName();
  12. }
  13. @Override
  14. public double getCost() {
  15. return this.handPanCake.getCost();
  16. }
  17. }

实现抽象装饰,并对关联的具体构件进行功能扩展

  1. /**
  2. * 配料具体实现 - 加鸡柳
  3. */
  4. class ChikenBatching extends Batching{
  5. public ChikenBatching(HandPanCake handPanCake) {
  6. super(handPanCake);
  7. }
  8. @Override
  9. public String getName() {
  10. return super.getName()+" 鸡柳";
  11. }
  12. @Override
  13. public double getCost() {
  14. return super.getCost()+4;
  15. }
  16. }
  17. /**
  18. * 配料具体实现 - 加培根
  19. */
  20. class BaconicBatching extends Batching{
  21. public BaconicBatching(HandPanCake handPanCake) {
  22. super(handPanCake);
  23. }
  24. @Override
  25. public String getName() {
  26. return super.getName()+" 培根";
  27. }
  28. @Override
  29. public double getCost() {
  30. return super.getCost()+3;
  31. }
  32. }
  33. /**
  34. * 配料具体实现 - 加火腿肠
  35. */
  36. class HamSausageBatching extends Batching{
  37. public HamSausageBatching(HandPanCake handPanCake) {
  38. super(handPanCake);
  39. }
  40. @Override
  41. public String getName() {
  42. return super.getName()+" 火腿肠";
  43. }
  44. @Override
  45. public double getCost() {
  46. return super.getCost()+2;
  47. }
  48. }

此时再去点手抓饼:

  1. HandPanCake handPanCake = new MyHandPanCake();
  2. System.out.println(handPanCake.getName()+" "+handPanCake.getCost());
  3. //加鸡柳
  4. HandPanCake chpc = new ChikenBatching(handPanCake);
  5. System.out.println(chpc.getName()+" "+chpc.getCost());
  6. //加培根
  7. HandPanCake bhpc = new BaconicBatching(chpc);
  8. System.out.println(bhpc.getName()+" "+bhpc.getCost());
  9. //加火腿肠
  10. HandPanCake hshpc = new HamSausageBatching(chpc);
  11. System.out.println(hshpc.getName()+" "+hshpc.getCost());

结果如下:

  1. 原味手抓饼 5.0
  2. 原味手抓饼 鸡柳 9.0
  3. 原味手抓饼 鸡柳 培根 12.0
  4. 原味手抓饼 鸡柳 火腿肠 11.0

此时,如果需要新增配料(具体装饰),仅需要增加一个继承了Batching (抽象装饰)的类,而后用这个类去包裹装饰即可,不必改动源码,符合开闭原则

实际上,装饰器模式根本性的关键在于:**组合优于继承,用组合的方式去替代反例1中的继承,从而可扩展地解决变化的需求,且不需要改变源码**

应用场景

当需要给类添加新的功能,但是用继承的方式又会导致子类爆炸的问题时,选择使用装饰器模式

当需要给类添加新的功能,且这项功能可以动态添加和撤销时
**

优点

装饰器抽象和装饰器具体独立发展,不会与抽象构件和具体构件相耦合,这样就增加了功能的灵活性
**

缺点

虽然装饰器模式避免了抽象构件的继承子类爆炸,但是装饰抽象的子类依旧很多

复杂装饰会使得类之间的组合关系变得复杂,代码可读性差

如有贻误,还请评论指正