装饰者模式:动态的将责任附加到对象上
Java I/O API也是使用此模式的
装饰模式的类图如下:
抽象组件(Component):抽象类,也可以是接口,规范了动态接收责任的对象。
具体组件(Concrete Component):定义一个将要接收附加责任的类,该组件可以单独使用,或者是被装饰者包装起来使用。
抽象装饰组件(Decorator):抽象类,也可以是接口,拥有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
具体装饰组件(Concrete ComponentA,Concrete ComponentB):负责装饰具体的组件,给具体的组件添加责任,装饰者中有一个实例变量,可以记录被装饰者;装饰者可以扩展Component的状态,也可以加上新的方法,新行为是通过在旧行为前面或者后面做一些计算来添加的。
举例说明:小明想开一个饮品店,需要制定饮品的分类以及价格,饮品的主要原料是水,其他的原料我们称之为调料,包括牛奶,白砂糖,咖啡粉,红茶,水果等等,加的调料不一样,饮品的价格也不一样,各种材料的价格如下:
原料 | 价格(元) |
水 | 1 |
牛奶 | 2 |
咖啡粉 | 2 |
红茶 | 1 |
绿茶 | 1 |
蔓越莓 | 2 |
柠檬 | 2 |
白砂糖 | 1 |
具体类图如下:
具体的代码如下所示:
1 //抽象类,相当于类图中的抽象组件,所有的被装饰者都继承该类,并实现其中的抽象方法 2 abstract class Drink{ 3 String description = "Unknow Drink"; 4 //定义获取描述的方法,返回变量description 5 public String getDescription(){ 6 return description; 7 } 8 //抽象方法,所有继承该抽象类的之类都需实现该方法 9 public abstract int cost(); 10 } 11 //具体实现类,相当于类图中的具体组件,一般认为是被装饰者 12 class Water extends Drink{ 13 public Water(){ 14 description = "水"; 15 } 16 @Override 17 public int cost() { 18 return 1; 19 } 20 } 21 22 //抽象装饰组件 23 abstract class Condiment extends Drink{ 24 Drink drink; 25 public Condiment(Drink drink){ 26 this.drink = drink; 27 } 28 public abstract String getDescription(); 29 } 30 /** 31 * 具体装饰组件:需要让装饰者能够持有被装饰者,做法如下: 32 * 1:定义一个被装饰者的实例变量 33 * 2:想办法让被装饰者记录在实例变量中,这里的做法是通过构造函数实现 34 * @author SAMSUNG 35 * 36 */ 37 class RedTea extends Condiment{ 38 public RedTea(Drink tmpDrink){ 39 super(tmpDrink); 40 } 41 //获取完整的描述,除了自己的描述外,还通过委托,得到被装饰者的描述,两者结合得到完整的描述 42 @Override 43 public String getDescription() { 44 return drink.getDescription()+" 加红茶"; 45 } 46 @Override 47 public int cost() { 48 return 2 + drink.cost(); 49 } 50 } 51 //具体装饰组件 52 class Sugar extends Condiment{ 53 public Sugar(Drink tmpDrink){ 54 super(tmpDrink); 55 } 56 @Override 57 public String getDescription() { 58 59 return drink.getDescription()+" 加白砂糖"; 60 } 61 @Override 62 public int cost() { 63 return 1 + drink.cost() ; 64 } 65 } 66 67 //具体装饰组件 68 class Lemon extends Condiment{ 69 public Lemon(Drink tmpDrink){ 70 super(tmpDrink); 71 } 72 @Override 73 public String getDescription() { 74 75 return drink.getDescription()+" 加柠檬"; 76 } 77 @Override 78 public int cost() { 79 return 1 + drink.cost() ; 80 } 81 } 82 83 84 //测试类 85 public class DrinkTest { 86 87 public static void main(String[] args) { 88 89 Drink drink1 = new Water(); 90 //打印不添加任何装饰者的被装饰者的信息 91 System.out.println(drink1.getDescription()+" 价钱:"+drink1.cost()); 92 //使用redtea装饰 93 drink1 = new RedTea(drink1); 94 System.out.println(drink1.getDescription()+" 价钱:"+drink1.cost()); 95 //使用sugar装饰 96 drink1 = new Sugar(drink1); 97 System.out.println(drink1.getDescription()+" 价钱:"+drink1.cost()); 98 //使用lemon装饰 99 drink1 = new Lemon(drink1); 100 System.out.println(drink1.getDescription()+" 价钱:"+drink1.cost()); 101 } 102 }
运行结果如下:
代码分析:
1、抽象类都可以换成接口,接口是一种极度抽象的类型,比抽象类更加抽象;
2、装饰抽象类(Condiment )持有一个被装饰者抽象类(Drink)的引用,通过构造函数,传入Drink的一个对象作为参数,并将该参数赋给引用,使得装饰者可以持有被装饰者;
3、所有具体的装饰者的构造函数都调用了super(tmpDrink)方法,具体实现依赖抽象,体现了依赖倒置原则,当有新的装饰者加入的时候,不需要修改代码,只需定义一个新的装类,并让该类继承装饰者抽象类,就可以成为一个新的具体装饰者,体现了对扩展开放,对修改关闭的设计原则;
4、由运行结果可知: 多个装饰者可以包装一个装饰者,叫做装饰者链;
5、装饰者(Condiment )与被装饰者(Water)具有相同的类型,这里虽然使用了继承,使用继承的的目的在于达到“类型匹配",而不是获得”行为“;如果依赖于继承,那么类的行为只能在编译时静态决定,反之,利用组合,可在运行时利用装饰者增加新的行为。
装饰者模式的优缺点:
优点:
1、可以给对象添加很多额外的行为,扩展对象的行为,比继承更具有灵活性;
2、通过不同的装饰类,或者多个装饰类的排列组合,在运行时改变对象的行为,使得对象具有更强大的功能;
缺点:
1、会产生很多的小对象,多度使用的话增加了系统的复杂性;
2、虽然比继承具有很高的灵活性,但是也意味着出错的可能性越高,排错比较麻烦。