初学设计模式【3】装饰模式——Decorator
装饰模式:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
如定义中说的那样,装饰模式可以动态的为对象添加新的职责,通过继承也能达到扩展类功能的目的。那么这两种方式的区别在哪里呢?虽然通过继承也能达到扩展功能但这种方式是在编译时静态决定的,所有子类都会继承到相同的行为,不具有弹性,应对未来变化的能力较低。而通过Decorator模式就可以在运行时动态扩展,而且被装饰对象可以自由组合新的行为,更易于应对未来的功能扩展。
设计原则:类应该对扩展开放,对修改关闭。这就是“开放关闭”原则。类应该是容易扩展的在不需要修改现有代码的情况下,这样的设计可以应对改变,而且不会引入新的bug。Decorator模式就很好的遵循了这个原则。
UML类图
分析:ConcreteComponent代表一个被装饰对象,扩展自抽象类Component;Decorator是装饰者共同实现的接口;派生自Decorator的为具体的装饰对象;至于装饰者与被装饰者共同派生自Component是为了达到“装饰者与被装饰者”的类型匹配,因为我们用Decorator装饰了ConcreteComponent后不能改变ConcreteComponent的类型;Component不实现具体的行为,只是为了达到前面所说的装饰与被装饰者的类型匹配。
实例
咖啡馆下单系统:顾客可以选择咖啡的类型,还可以选择加入一种或多种调料,最后系统可以根据不同的咖啡类型及所加调料计算出总金额,打印出清单。
显然,我们用调料来装饰具体咖啡,uml图如下:
截自:《HeadFirst设计模式》
Beverage
1 public abstract class Beverage { 2 String description = "Unknown Beverage"; 3 4 public String getDescription() { 5 return description; 6 } 7 8 public abstract float cost(); 9 }
Decaf
1 public class Decaf extends Beverage { 2 3 public Decaf(String description) { 4 super.description = description; 5 } 6 7 @Override 8 public float cost() { 9 return 1.2f; 10 } 11 12 }
Espresso
1 public class Espresso extends Beverage { 2 3 public Espresso(String descraption) { 4 super.description = descraption; 5 } 6 7 @Override 8 public float cost() { 9 return 1.00f; 10 } 11 12 }
CondimentDecorator
1 public abstract class CondimentDecorator extends Beverage { 2 @Override 3 public abstract String getDescription(); 4 5 }
Milk
1 public class Milk extends CondimentDecorator { 2 3 private Beverage beverage; // 代表被装饰对象 4 5 public Milk(Beverage beverage) { 6 this.beverage = beverage; 7 } 8 9 @Override 10 public String getDescription() { 11 return beverage.getDescription() + "+ Milk"; 12 } 13 14 @Override 15 public float cost() { 16 return 0.31f + beverage.cost(); 17 } 18 19 }
Mocha
1 public class Mocha extends CondimentDecorator { 2 3 private Beverage beverage; // 代表被装饰对象 4 5 public Mocha(Beverage beverage) { 6 this.beverage = beverage; 7 } 8 9 @Override 10 public String getDescription() { 11 return beverage.getDescription() + "+ Mocha"; 12 } 13 14 @Override 15 public float cost() { 16 return 0.11f + beverage.cost(); 17 } 18 19 }
Soy
1 public class Soy extends CondimentDecorator { 2 private Beverage beverage; // 代表被装饰对象 3 4 public Soy(Beverage beverage) { 5 this.beverage = beverage; 6 } 7 8 @Override 9 public String getDescription() { 10 return beverage.getDescription() + "+ Soy"; 11 } 12 13 @Override 14 public float cost() { 15 return 0.21f + beverage.cost(); 16 } 17 18 }
测试
1 import org.junit.Test; 2 3 public class TestDecorator { 4 5 @Test 6 public void test() { 7 Beverage beverage = new Decaf("Decaf"); 8 beverage = new Milk(beverage); 9 beverage = new Milk(beverage); 10 beverage = new Mocha(beverage); 11 System.out.println(beverage.getDescription() + ":" + beverage.cost()); 12 } 13 }
测试结果:(双倍牛奶+摩卡+无咖啡因咖啡)
总结
1)装饰模式的根类Component一般为abstract或接口类型,设置此类的目的只为了达到装饰者与被装饰者类型的匹配,不实现具体的行为,这一点很重要,可以结合上面的例子仔细体会。
2)装饰模式也有缺点:会增加我们所要维护类的数量。此模式一般与“工厂模式”一起使用,可克服此问题。
3)装饰模式可以达到运行时动态扩展系统功能的目的,通过不断扩展新的具体装饰类可以提高应对未来变化的能力。
4)java IO类就是依照这个模式设计的