1. 一个咖啡店的需求 一个咖啡店需要升级他们的系统,以满足饮料订单的需求。我们首先设计了一个类结构:
Beverage类是一个抽象类,其中cost是一个抽象方法,每个子类需要实现这个方法,以完成自己的定价。description是饮料的描述信息。
这样的设计初看上去是没有问题的,但是,顾客的需求是多变的,顾客可能会要求向饮料中加入一些其他的调味品,例如牛奶、可可等。咖啡店需要对这些添加的调味品收取一定的费用。于是,类图变成的下面这样。
无疑,这是一个糟糕的设计,为每一种添加不同调味品的饮料单独生成一个类,造成了类数量随调味品数量增加而指数扩展的现象。可以想象,如果某一个调味品的价格发生变化,那么对类进行修改将是一个灾难事件。
1.1. 让超类管理调味品价格怎么样? 首先我们来尝试让超类负责管理调味品价格,看结果是怎么样呢?
我们把Beverage类设计成这样,那么一个饮料如果需要添加milk,在下单时调用hasMilk()函数就可以加上milk的价格,milk的价格发生变化时,调用setMilk()
这样的设计比一开始的设计要好,但是仍然存在问题:
如果购买了新的调味品,就需要修改Beverage类 
有些饮料可能并不需要这些调味品,例如茶(这里的情况和第一章的情况类似,并不是所有的鸭子都会飞) 
如果顾客加了双份的milk怎么办呢? 
 
2. 开闭原则(The Open-Closed Principle) Classes should be open for extension, but closed for modification.
类应该对扩展开放,对修改关闭。
我们的目标是在不改变原有代码的前提下,让类可以方便的扩展以拥有新的行为。
需要注意的是,在任何地方都使用开闭原则是浪费的和不必要的,它会导致复杂和那一理解的代码。
3. 装饰模式 3.1. 如何操作 我们会使用Beverage并且在运行时使用调味品对其进行装饰。例如,顾客想要一杯加Mocha和Whip的DarkRoast,我们会这样做:
调用DarkRoast类,产生一个DarkRoast对象 
用Mocha对象装饰它 
用Whip对象装饰它 
调用cost()方法并依赖委托(delegation)来添加调味品的价格 
 
可以把装饰对象想成是一个包装,我们用Mocha对象包装了DarkRoast对象,随后又用Whip对象包装。
接下来该计算价格了,在最外层的装饰上调用cost()方法,Whip将要去被委托计算它所装饰的对象的价格,它获得一个价格就会添加到Whip的总价上
通过上面的分析我们知道:
装饰器和他们所装饰的对象有相同的超类 
可以使用一个或多个装饰器对对象进行装饰 
由于decorator与它所装饰的对象具有相同的超类,我们可以传递一个装饰对象来代替原始(包装)对象。 
The decorator adds its own behavior either before and/or after delegating to the object it decorates to do the rest of the job  (装饰器在委派给其装饰的对象之前和/或之后添加自己的行为,以完成其余工作。)对象可以在任何时间被装饰,所以我们可以在运行时为对象添加任意多的装饰器 
 
3.2. 定义 1 2 The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. 
3.3. 使用装饰模式重新设计 
3.3.1. 代码 完整代码 
3.3.1.1 Beverage类 1 2 3 4 5 6 7 8 9 public  abstract  class  Beverage      String description = "Unkown Bervage" ;     public  String getDescription ()           return  description;     }     public  abstract  double  cost ()  } 
3.3.1.2. HouseBlend类 1 2 3 4 5 6 7 8 9 10 public class HoseBlend extends Beverage{     public HoseBlend() {         description = "HoseBlend";     }     @Override     public double cost() {         return 0.89;     } } 
3.3.1.3. CondimentDecorator类 1 2 3 public  abstract  class  CondimentDecorator  extends  Beverage     public  abstract  String getDescription ()  } 
3.3.1.4. Milk类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public  class  Milk  extends  CondimentDecorator      private  Beverage beverage;     public  Milk (Beverage beverage)           this .beverage = beverage;     }     @Override      public  String getDescription ()           return  beverage.getDescription() + ", Milk" ;     }     @Override      public  double  cost ()           return  beverage.cost() + 0.5 ;     } } 
3.3.1.5. 测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  class  CoffeShop      public  static  void  main (String[] args)           Beverage beverage = new  Espresso();         System.out.println(beverage.getDescription() + " $"  + beverage.cost());         Beverage beverage1 = new  DarkRoast();         beverage1 = new  Mocha(beverage1);         beverage1 = new  Mocha(beverage1);         beverage1 = new  Whip(beverage1);         System.out.println(beverage1.getDescription() + " $"  + beverage1.cost());         Beverage beverage2 = new  HoseBlend();         beverage2 = new  Soy(beverage2);         beverage2 = new  Mocha(beverage2);         beverage2 = new  Whip(beverage2);         System.out.println(beverage2.getDescription() + " $"  + beverage2.cost());     } } 
 执行结果
1 2 3 4 5 Espresso $1.99 DarkRoast, Mocha, Mocha, Whip $1.8 HoseBlend, Soy, Mocha, Whip $1.79 Process finished with exit code 0 
4. 添加需求 咖啡店设置了3中类型的杯子(小,中,大),并且调味品根据杯子的大小收费不同,例如Soy对应(小,中,大)杯的价格分别为0.1,0.2,0.3,代码应该怎么实现呢?
4.1. 代码 完整代码 
我只改动了以下3个类,测试时需要打开注释的部分。
4.1.1. Beverage类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public  abstract  class  Beverage      String description = "Unkown Bervage" ;     public  enum  Size {SMALL, MEDIUM, LARGE};     Size size = Size.SMALL;     public  Size getSize ()           return  size;     }     public  void  setSize (Size size)           this .size = size;     }     public  String getDescription ()           return  description;     }     public  abstract  double  cost ()  } 
在原有的代码基础上添加了
1 2 3 4 5 6 7 8 9 10 public enum Size {SMALL, MEDIUM, LARGE};    Size size = Size.SMALL;    public Size getSize() {        return size;    }    public void setSize(Size size) {        this.size = size;    } 
4.1.2. HoseBlend类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public  class  HoseBlend  extends  Beverage     private  Map<Size, Double> price = new  HashMap<Size, Double>();     public  HoseBlend ()           description = "HoseBlend" ;         price.put(Size.SMALL, 0.1 );         price.put(Size.MEDIUM, 0.2 );         price.put(Size.LARGE, 0.3 );     }     @Override      public  double  cost ()           return  price.get(getSize());     } } 
4.1.3. Milk类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class  Milk  extends  CondimentDecorator      private  Beverage beverage;     private  Map<Size, Double> price = new  HashMap<Size, Double>();     public  Milk (Beverage beverage)           this .beverage = beverage;         setSize(beverage.getSize());          price.put(Size.SMALL, 0.11 );         price.put(Size.MEDIUM, 0.15 );         price.put(Size.LARGE, 0.31 );     }     @Override      public  String getDescription ()           return  beverage.getDescription() + ", Milk" ;     }     @Override      public  double  cost ()           return  beverage.cost() + price.get(getSize());     } } 
4.1.4. 测试 1 2 3 4 5 6 7 8 9 10 11 12 13 public  class  CoffeShop      public  static  void  main (String[] args)           everage beverage3 = new  HoseBlend();         beverage3.setSize(Beverage.Size.MEDIUM);         beverage3 = new  Milk(beverage3);         System.out.println("Cup Size "  + beverage3.getSize() + " "  + beverage3.getDescription() + " $"  + beverage3.cost());         Beverage beverage4 = new  HoseBlend();         beverage4.setSize(Beverage.Size.LARGE);         beverage4 = new  Milk(beverage4);         System.out.println("Cup Size "  + beverage4.getSize() + " "  + beverage4.getDescription() + " $"  + beverage4.cost());     } } 
执行结果:
1 2 Cup Size MEDIUM HoseBlend, Milk $0.35 Cup Size LARGE HoseBlend, Milk $0.61 
5. 真实工程中的装饰模式的应用 java.io 中就使用了装饰模式
InputStream是抽象类
FilterInputStream是一个抽象装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public  class  Test1      public  static  void  main (String[] args)           int  c;         try  {             File file = new  File("test" );             InputStream stream = new  FileInputStream(file);             stream = new  BufferedInputStream(stream);             stream = new  LineInputStream(stream);             while  ((c = stream.read()) >= 0 ) {                 System.out.print((char ) c);             }             stream.close();         } catch  (IOException e) {             e.printStackTrace();         }     } } 
5.2. 自定义java I/O装饰器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public  class  LowerCaseInputStream  extends  FilterInputStream           protected  LowerCaseInputStream (InputStream in)           super (in);     }     public  int  read ()  throws  IOException          int  c = in.read();         return  (c == -1 ?c : Character.toLowerCase((char ) c));     }     public  int  read (byte [] b, int  offset, int  len)  throws  IOException          int  result = in.read(b, offset, len);         for  (int  i=offset; i<offset+result; i++) {             b[i] = (byte )Character.toLowerCase((char )b[i]);         }         return  result;     } } 
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public  class  Test2      public  static  void  main (String[] args)           int  c;         try  {             InputStream in = new  LowerCaseInputStream(                     new  BufferedInputStream(                             new  FileInputStream("test" )));             while  ((c = in.read()) >= 0 ) {                 System.out.print((char )c);             }         } catch  (IOException e) {             e.printStackTrace();         }     } }