概念
在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
允许向一个现有的对象添加新的功能,同时又不改变其结构
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
角色:
抽象构件(Component)角色:定义一个抽象接口用于规范核心对象。
具体构件(Concrete Component)角色:核心对象,实现抽象构件,被装饰的角色。
抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,通过子类对具体构件进行功能扩展。
具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,对具体构件进行功能扩展。
推演
需求**:手抓饼(HandPanCake)加上不同的配料:如鸡柳(Chicken)、培根(Baconic)、火腿肠(Ham sausage)等**
首先创建一个手抓饼标准抽象
/*** 手抓饼接口*/abstract class HandPanCake{/*** 获取描述* @return*/abstract String getName();/*** 获取价格* @return*/abstract double getCost();}
创建实例实现上述方法,也就是具体的手抓饼类型
/*** 原味手抓饼*/class OriginalCake extend HandPanCake{@Overridepublic String getName() {return "原味手抓饼";}@Overridepublic double getCost() {return 5;}}
点一个原味手抓饼
HandPanCake oc = new OriginalCake();System.out.println(oc.getName()+" "+oc.getCost());
结果:
原味手抓饼 5.0
这时候想要获取到不同搭配的手抓饼怎么办呢?
反例1(传统套路1)
手抓饼是核心不变的,不同的是配料(鸡柳,培根,火腿肠),我们可以创建新的子类中加入配料
/*** 原味手抓饼+鸡柳*/class WithChickenCake extend HandPanCake{@Overridepublic String getName() {return "原味手抓饼 鸡柳";}@Overridepublic double getCost() {return 5+4;}}/*** 原味手抓饼+鸡柳*/class WithHamSausageCake extend HandPanCake{@Overridepublic String getName() {return "原味手抓饼 火腿肠";}@Overridepublic double getCost() {return 5+2;}}/*** 原味手抓饼+鸡柳+火腿肠*/class ChickenWithHamSausageCake extend HandPanCake{@Overridepublic String getName() {return "原味手抓饼 鸡柳 火腿肠";}@Overridepublic double getCost() {return 5+4+2;}}
明显上述方法很不合适,因为变化无穷而产生的组合无穷,极易引起类爆炸
反例2(传统套路2)
在父类中直接包含配料即可,有客户端选择是否添加,假设可以更改父类代码,则将手抓饼标准更改如下:
abstract class HandPanCake{//配料boolean addChicken,addBaconic,addHamSausage;/*** 获取描述* @return*/abstract String getName();/*** 获取价格* @return*/abstract double getCost();/*** 是否添加鸡柳* @param bool*/void setAddChicken(boolean bool){this.addChicken = bool;}/*** 是否添加培根* @param bool*/void setAddBaconic(boolean bool){this.addBaconic = bool;}/*** 是否添加火腿肠* @param bool*/void setAddHamSausage(boolean bool){this.addHamSausage = bool;}}
手抓饼具体实现:
class MyHandPanCake extends HandPanCake{@Overridepublic String getName() {String name = "原味手抓饼";if(addChicken){name = name +" 鸡柳";}if(addBaconic){name = name +" 培根";}if(addHamSausage){name = name +" 火腿肠";}return name;}@Overridepublic double getCost() {double sum = 5;//原价if(addChicken){sum = sum+4;}if(addBaconic){sum= sum+3;}if(addHamSausage){sum= sum+2;}return sum;}}
客户点手抓饼:
HandPanCake mhpc = new MyHandPanCake();mhpc.setAddBaconic(true);// 加培根System.out.println(mhpc.getName()+" "+mhpc.getCost());mhpc.setAddChicken(true); // 加鸡柳System.out.println(mhpc.getName()+" "+mhpc.getCost());
结果如下:
原味手抓饼 培根 8.0原味手抓饼 鸡柳 培根 12.0
类的数量是变少了,但是封装过甚,如果加了配料,那么必须修改源码加上去,违反了开闭原则
正例
保证抽象构件和具体构件稳定
abstract class HandPanCake{/*** 获取描述* @return*/abstract String getName();/*** 获取价格* @return*/abstract double getCost();}/*** 原味手抓饼*/class MyHandPanCake extends HandPanCake {@Overridepublic String getName() {return "原味手抓饼";}@Overridepublic double getCost() {return 5;}}
增加抽象装饰,实现抽象构件接口,并关联抽象构建:
/*** 装饰器抽象 - 配料*/abstract class Batching extends HandPanCake{private HandPanCake handPanCake;public Batching(HandPanCake handPanCake){this.handPanCake = handPanCake;}@Overridepublic String getName() {return this.handPanCake.getName();}@Overridepublic double getCost() {return this.handPanCake.getCost();}}
实现抽象装饰,并对关联的具体构件进行功能扩展
/*** 配料具体实现 - 加鸡柳*/class ChikenBatching extends Batching{public ChikenBatching(HandPanCake handPanCake) {super(handPanCake);}@Overridepublic String getName() {return super.getName()+" 鸡柳";}@Overridepublic double getCost() {return super.getCost()+4;}}/*** 配料具体实现 - 加培根*/class BaconicBatching extends Batching{public BaconicBatching(HandPanCake handPanCake) {super(handPanCake);}@Overridepublic String getName() {return super.getName()+" 培根";}@Overridepublic double getCost() {return super.getCost()+3;}}/*** 配料具体实现 - 加火腿肠*/class HamSausageBatching extends Batching{public HamSausageBatching(HandPanCake handPanCake) {super(handPanCake);}@Overridepublic String getName() {return super.getName()+" 火腿肠";}@Overridepublic double getCost() {return super.getCost()+2;}}
此时再去点手抓饼:
HandPanCake handPanCake = new MyHandPanCake();System.out.println(handPanCake.getName()+" "+handPanCake.getCost());//加鸡柳HandPanCake chpc = new ChikenBatching(handPanCake);System.out.println(chpc.getName()+" "+chpc.getCost());//加培根HandPanCake bhpc = new BaconicBatching(chpc);System.out.println(bhpc.getName()+" "+bhpc.getCost());//加火腿肠HandPanCake hshpc = new HamSausageBatching(chpc);System.out.println(hshpc.getName()+" "+hshpc.getCost());
结果如下:
原味手抓饼 5.0原味手抓饼 鸡柳 9.0原味手抓饼 鸡柳 培根 12.0原味手抓饼 鸡柳 火腿肠 11.0
此时,如果需要新增配料(具体装饰),仅需要增加一个继承了Batching (抽象装饰)的类,而后用这个类去包裹装饰即可,不必改动源码,符合开闭原则
实际上,装饰器模式根本性的关键在于:**组合优于继承,用组合的方式去替代反例1中的继承,从而可扩展地解决变化的需求,且不需要改变源码**
应用场景
当需要给类添加新的功能,但是用继承的方式又会导致子类爆炸的问题时,选择使用装饰器模式
优点
装饰器抽象和装饰器具体独立发展,不会与抽象构件和具体构件相耦合,这样就增加了功能的灵活性
**
缺点
虽然装饰器模式避免了抽象构件的继承子类爆炸,但是装饰抽象的子类依旧很多
复杂装饰会使得类之间的组合关系变得复杂,代码可读性差
如有贻误,还请评论指正
