需求分析

有一家咖啡店,生意火爆,目前正在大量新增新型咖啡。
目前有

  1. HouseBlend 首选咖啡 、
  2. DarkRoast 烘焙咖啡、
  3. Decaf 低咖、
  4. Espresso 浓缩咖啡、

每样咖啡都有属于自己的cost 价钱 以及描述。

第一次设计

定义饮料超类


  1. public abstract class Beverage {
  2. String description;
  3. public String getDescription() {
  4. return description;
  5. }
  6. abstract void cost();
  7. }

定义首选咖啡


  1. public class HouseBlend extends Beverage {
  2. @Override
  3. public String getDescription() {
  4. description = "我是 HouseBlend ";
  5. return description;
  6. }
  7. void cost() {
  8. System.out.println("我价值 1 元");
  9. }
  10. }

定义烘焙咖啡


  1. public class DarkRoast extends Beverage {
  2. @Override
  3. public String getDescription() {
  4. description = "我是 darkRoast";
  5. return description;
  6. }
  7. void cost() {
  8. System.out.println("我价值 2 元");
  9. }
  10. }

测试


  1. public class Test {
  2. public static void main(String[] args) {
  3. Beverage houseBlend = new HouseBlend();
  4. System.out.println(houseBlend.getDescription());
  5. houseBlend.cost();
  6. Beverage darkRoast = new DarkRoast();
  7. System.out.println(darkRoast.getDescription());
  8. darkRoast.cost();
  9. }
  10. }
  11. **结果
  12. 我是 HouseBlend
  13. 我价值 1
  14. 我是 darkRoast
  15. 我价值 2

新需求来了

资本家现在想给原有的咖啡中加上调料, 例如蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha)···
加上调料就要有不同的产品描述和新的价格。
于是,他们这样做····

首选咖啡搭配蒸奶和摩卡 第一个解决方案


  1. public class HouseBlendWithSteamedMilkAndMocha extends Beverage {
  2. @Override
  3. public String getDescription() {
  4. description = "我是 HouseBlendWithSteamedMilkAndMocha";
  5. return description;
  6. }
  7. void cost() {
  8. System.out.println("我的价值是 hoseBlend + SteamedMill + Mocha 实为 1 + 1 + 1 = 3元");
  9. }
  10. }

·····
我有无数中搭配方案,那么就有无数个新类。

note: 这样肯定是不可取的,首先增加维护成本,其次如果要修改一种调料的价格,会发现 牵一发而动全身了。

第二个解决方案

将所有的调料放在超类中,根据boolean类型来判断子类是否需要调料

新的饮料抽象超类


  1. public abstract class Beverage {
  2. public String description;
  3. boolean milk;
  4. boolean soy;
  5. boolean mocha;
  6. public String getDescription() {
  7. return description;
  8. }
  9. void cost(){
  10. if(isMilk()){
  11. System.out.println("加milk,2元");
  12. }
  13. if(isMocha()){
  14. System.out.println("加mocha,3元");
  15. }
  16. if(isSoy()){
  17. System.out.println("加soy,5元");
  18. }
  19. };
  20. public boolean isMilk() {
  21. return milk;
  22. }
  23. public void setMilk(boolean milk) {
  24. this.milk = milk;
  25. }
  26. public boolean isSoy() {
  27. return soy;
  28. }
  29. public void setSoy(boolean soy) {
  30. this.soy = soy;
  31. }
  32. public boolean isMocha() {
  33. return mocha;
  34. }
  35. public void setMocha(boolean mocha) {
  36. this.mocha = mocha;
  37. }
  38. }

新的烘焙咖啡DarkRoast


  1. public class DarkRoast extends Beverage {
  2. @Override
  3. public String getDescription() {
  4. description = "我是 darkRoast";
  5. return description;
  6. }
  7. void cost() {
  8. System.out.println("我价值 2 元");
  9. setMilk(true);
  10. super.cost();
  11. }
  12. }

新的首选咖啡HouseBlend


  1. public class HouseBlend extends Beverage {
  2. @Override
  3. public String getDescription() {
  4. description = "我是 HouseBlend ";
  5. return description;
  6. }
  7. void cost() {
  8. System.out.println("我价值 1 元");
  9. setMocha(true);
  10. setSoy(true);
  11. super.cost();
  12. }
  13. }

测试


  1. Beverage darkRoast =new DarkRoast();
  2. System.out.println(darkRoast.getDescription());
  3. darkRoast.cost();
  4. Beverage houseBlend = new HouseBlend();
  5. System.out.println(houseBlend.getDescription());
  6. houseBlend.cost();
  7. 我是 darkRoast
  8. 我价值 2
  9. milk2
  10. 我是 HouseBlend
  11. 我价值 1
  12. mocha3
  13. soy5

note: 可以发现,超类将所有调料的管理起来了,利用继承解决了类爆炸问题,弥补了第一种解决方案的不足。

不足之处

  1. 一旦出现新的调料,那么超类中的cost方案就要修改一次。
  2. 有些饮料不需要这些某些调料,但是用继承的话就会继承到那些无用的变量

    第二次设计,巧妙利用组合与委托代替继承

    note : 因为要返回饮料的价格,所以适当调整了cost方法, 并且定义了描述的默认值:未知饮料

超类 Beverage


  1. public abstract class Beverage {
  2. String description = "Unknown Beverage";
  3. public String getDescription() {
  4. return description;
  5. }
  6. public abstract double cost();
  7. }

调味品装饰类 CondimentDecorator

首先,装饰类是一个抽象类
其次,必须能让装饰类能够替代被装饰类。 这边用继承达到这个效果。
再者,它要重写getDescription方法

  1. public abstract class CondimentDecorator extends Beverage {
  2. public abstract String getDescription();
  3. }

首选咖啡HouseBlend 继承自Beverage


  1. public class HouseBlend extends Beverage {
  2. public HouseBlend(){
  3. description = "House Blend Coffee";
  4. }
  5. public double cost() {
  6. return 0.89;
  7. }
  8. }

烘焙咖啡 DarkRoast 继承自Beverage


  1. public class DarkRoast extends Beverage {
  2. public DarkRoast(){
  3. description = "Dark Roast Coffee";
  4. }
  5. public double cost() {
  6. return 1.9;
  7. }
  8. }

调料 Mocha 继承自 CondimentDecorator


  1. public class Mocha extends CondimentDecorator {
  2. Beverage beverage;
  3. public Mocha(Beverage beverage){
  4. this.beverage = beverage;
  5. }
  6. public String getDescription() {
  7. return beverage.getDescription() + ", Mocha";
  8. }
  9. public double cost() {
  10. return 1.2 + beverage.cost();
  11. }
  12. }

调料Soy 继承自 CondimentDecorator


  1. public class Soy extends CondimentDecorator {
  2. Beverage beverage;
  3. public Soy(Beverage beverage){
  4. this.beverage = beverage;
  5. }
  6. public String getDescription() {
  7. return beverage.getDescription() + ", Soy";
  8. }
  9. public double cost() {
  10. return 3.3 + beverage.cost();
  11. }
  12. }

测试:

定一杯纯HouseBlend

  1. public static void main(String[] args) {
  2. Beverage beverage = new HouseBlend();
  3. System.out.println(beverage.getDescription() + " ¥"+beverage.cost());
  4. }
  5. //结果
  6. House Blend Coffee 0.89

定一杯HouseBlend + Soy

  1. public static void main(String[] args) {
  2. Beverage beverage = new HouseBlend();
  3. beverage = new Soy(beverage); //用soy装饰HouseBlend
  4. System.out.println(beverage.getDescription() + " ¥"+beverage.cost());
  5. }
  6. //结果:
  7. House Blend Coffee, Soy 4.1899999999999995

定一杯DarkRoast + soy + mocha

  1. public static void main(String[] args) {
  2. Beverage beverage = new DarkRoast();
  3. beverage = new Soy(beverage);
  4. beverage = new Mocha(beverage); //两个一起装饰 darkroast
  5. System.out.println(beverage.getDescription() + " ¥"+beverage.cost());
  6. }
  7. //结果
  8. Dark Roast Coffee, Soy, Mocha 6.3999999999999995

小结

在最后一次设计中,所有的调料变成了装饰者。
当一杯饮料需要用到调料时,只需要用该调料装饰着给饮料包裹一层就好了。
比如:


  1. new Soy(某饮料)

note : 会发现这一套下来有些像链式编程。 外层装饰者调用内层饮料。


下定义


装饰者模式 动态地将责任附加到对象上! 若要扩展功能,装饰者提供了比继承更有弹性的替代方案(组合+委托) 装饰者模式完全遵循了 开放-闭合的设计原则, 即对扩展开发,对修改关闭。


JDK实现


JAVA/IO
BufferInputStream 就是FileInputStream的装饰者


探索


前面说到,装饰类必须能够替代被装饰者类。
那么猜测, BufferInputStream这个具体装饰者实现类,它的超类一定继承于FileInputStream的超类,也就是说一定继承于InputStream抽象类。