学习笔记之装饰者模式
在讨论装饰者模式之前我们先来说一下继承和组合
尽管继承威力巨大,但是它并不是总能实现最有弹性最好维护的设计,但是通过组合和委托可以在运行时具有继承行为的效果。利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态的进行扩展。通过动态的组合对象,可以写新的代码添加新的功能,而无需修改现有的代码。既然没有改变现有的代码,那么引起bug和意外副作用的机会将大幅减少。
所以代码应该如同晚霞中的莲花一样地关闭(免于改变),如同晨曦中的莲花一样开放(能够扩展)这也引出了一个开放-关闭设计原则:对扩展开放对修改关闭。
接下来认识装饰者模式
首先讨论一个例子,星巴克咖啡
如果按照这个类图来写的话,真是类爆炸,这不仅违反了面向接口编程而不面向实现编程的原则,还违反了多用组合少用继承的原则。真是事倍功半,超级麻烦并且代码也是超级的烂,保证你写完绝对不会再去想看第二眼,简直是一个维护噩梦,但是想一想还有没有更好的方法,如果你了解了观察者模式,简直是所有的问题都解决了
我们可以以饮料为主题,然后在运行时以调料来“装饰”饮料。
下面展示几张图片你就理解了
从图上我们可以得到
1. 装饰者和被装饰者具有相同的类型
2. 你可以用一个或多个装饰者包装一个对象
3. 既然装饰者和被装饰者拥有相同的超类型,所以在任何需要原始对象的的场合,都可以用装饰过的对象代替
4. 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,已达到特定的目的
5. 对象可以在任何时候被装饰,所以可以运行时动态地。不限量地用你喜欢的装饰者来装饰对象
定义装饰者模式
装饰者模式动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
装饰者模式的类图
让星巴克咖啡也符合装饰者模式的类图
此处插入代码
package decorate;
public abstract class Beverage {
public enum Size{TALL,GRANDE,VENTI};
Size size=Size.TALL;
String description="unkonow";
public String getDiscription(){
return description;
}
public void setSize(Size size){
this.size=size;
}
public Size getSize(){
return this.size;
}
public abstract double cost();
}
package decorate;
/*为了节约时间此处我只写了一种饮料类型*/
public class HouseBlend extends Beverage {
//通过构造函数来传递参数
public HouseBlend() {
description="HouseBlend";
}
@Override
public double cost() {
return 0.89;
}
}
package decorate;
public abstract class CondimentDecorator extends Beverage {
public Beverage beverage;
public abstract String getDiscription();
public Size getSize(){
return beverage.getSize();
}
}
package decorate;
public class Milk extends CondimentDecorator {
Beverage beverage;
//为了把装饰者记录到实例变量中,通过把饮料当做构造器的参数,再由构造器将此饮料记录到实例变量中。
public Milk(Beverage beverage) {
this.beverage=beverage;
}
public String getDiscription(){
return beverage.getDiscription()+",Milk";
}
@Override
public double cost() {
return beverage.cost()+0.20;
}
}
package decorate;
public class Mocha extends CondimentDecorator{
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage=beverage;
}
@Override
public String getDiscription() {
return beverage.getDiscription()+",Mocha";
}
@Override
public double cost() {
return beverage.cost()+0.20;
}
}
package decorate;
public class Soy extends CondimentDecorator {
Beverage beverage;
public Soy(Beverage beverage) {
this.beverage=beverage;
}
@Override
public String getDiscription() {
return beverage.getDiscription()+",Soy";
}
@Override
public double cost() {
double cost = beverage.cost();
if (beverage.getSize() == Size.TALL) {
cost += 0.10;
} else if (beverage.getSize() == Size.GRANDE) {
cost += 0.15;
} else if (beverage.getSize() == Size.VENTI) {
cost += 0.20;
}
return cost;
}
}
package decorate;
public class Whip extends CondimentDecorator {
Beverage beverage;
public Whip(Beverage beverage) {
this.beverage=beverage;
}
@Override
public String getDiscription() {
return beverage.getDiscription()+",Whip";
}
@Override
public double cost() {
return beverage.cost()+0.10;
}
}
package decorate;
import decorate.Beverage.Size;
public class Test {
public static void main(String[] args) {
Beverage beverage=new HouseBlend();
System.out.println(beverage.getDiscription()+"$"+beverage.cost());
Beverage beverage2 = new HouseBlend();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDiscription()
+ " $" + beverage2.cost());
Beverage beverage3=new HouseBlend();
beverage3.setSize(Size.VENTI);
beverage3=new Soy(beverage3);
beverage3=new Mocha(beverage3);
beverage3=new Whip(beverage3);
System.out.println(beverage3.getDiscription()+beverage3.cost());
}
}
装饰者模式总结:
装饰者模式有能力为设计注入弹性,但也有“黑暗面”,有时候会再说设计中注入大量的小类,这偶尔导致别人不容易了解装饰者模式,有时候人们在客户代码中依赖某种特定类型,然后忽然导入装饰者,却又没有周详的考虑一切,不过通常可以透明的插入装饰者,客户程序不需要知道它在和装饰者打交道。还有就是,在采用装饰者实例化组件时,将增加代码的复杂度。一旦使用装饰者模式,不只需要实例化组件,还要把此组件包装进装饰者中。不过装饰者模式依然是一个很好的模式,适合用来建立有弹性的设计,维持开放-关闭原则。