组合的魔力——模式系列谈之Decorator模式

                                                                        组合的魔力
                                                             ——模式系列谈之Decorator模式
一、上帝的智慧
《圣经》上说上帝花了六天造好世间万物。我们就会感叹,世间万物,何止上万,上帝是如何一个一个的造出来的呢?他老人家要是一个一个的造来,不要说费心劳力,光过程的繁琐、单调、乏味都不是常人所能够忍受的,虽然他老人家是神,忍耐力要比凡人好得多,但也不一定能承受下来。
 哈哈,不用瞎操心,上帝自有他老人家的智慧。他老人家大手一挥,造出108种元素出来(这可要容易得多),然后他老人家撒手不管了,让这些元素去自由组合。比如,他老人家造出C、H、O来,于是C可以自己组成碳,H自己组成氢气,O自己组成氧气,C和O组成二氧化碳,H和O组成水,等等。
 上帝的大智慧,其实就是组合的魔力。在我们的软件编码过程中,也会遇到各种各样的类,这些类,虽然我们用到了继承,但有时候也是很不够用的。
 请看一个例子:
二、从一个例子说起
我们经常喜欢去咖啡馆喝咖啡。咖啡的种类也是非常之多:有黑咖啡(原咖啡)、加冰、加糖、加奶、加巧克力、加冰加糖、加奶加糖、加冰加奶等等。
 假如我们现在希望帮咖啡馆设计一个咖啡贩卖系统。我们直接的想法就是:第一、给黑咖啡设计一个类Coffee;第二、给加冰咖啡设计一个继承Coffee类的类IceCoffee;……
 依次类推,会得出二十多个类,我们可以看到,这里的类已经相当庞大了,已经是相当冗余的一个系统了。但这还不是最可怕的,最可怕的是,如果咖啡馆又推出来新的种类,如蒸汽加压咖啡,那么这个系统又该产生多少个类来呢?
 很显然,在这里,我们要借助上帝的智慧了。想法很简单:我们是不是也能设定一些基本的类,然后再由这些基本的类来生成各种各样的对象呢?
 这种方法就是我们要讲到的Decorator模式。
三、Decorator模式
 谈到Decorator模式,我们还是先从组合谈起。
 假设有类A:
 class A
 {
  public void Ado()
  {
 ……
 }
 }
 类B:
 class B
 {
  public void Bdo()
  {
   ……
  }
 }
 那么类C为他们之间的组合:
 class C
 {
  private A a;
  private B b;
  public C()
  {
   a = new A();
   b = new B();
 }
 public void Cdo()
 {
a.Ado();
b.Bdo();
   ……
  }
 }
 这是我们最能理解的类的组合,其中A类和B类能单独存在,然后他们又共同组成C类。
 现在我们要考虑这样一种组合的模式,如上面的咖啡的例子中:咖啡,也就是黑咖啡可以单独组成一个类;而加冰这个类却是咖啡加冰,而不是单独的冰这个类;同样,加糖这个类也是咖啡加糖,而不是单独的糖这个类;……
 这点理解是十分重要的,Decorate是装饰的意思,而Decorator则是装饰者的意思。很明显:Decorator模式中只有一个类是主体类,其他的都是Decorator类,要依附于主体类。
 如,上面的咖啡例子中,咖啡是一个主体类,而其他的如冰、糖等都是Decorator类,都要依附于主体类,成为咖啡加冰类、咖啡加糖类……不能单独存在。
 上例中,如果我们的类A为主体类,那么B类不能单独存在,C类却需要修改如下:
 class AB
 {
  private A a;
  public AB()
  {
   a = new A();
 }
  public ABdo()
  {
a.Ado();
//do what B will do
……
 }
 }
 这种组合方式构成了我们Decorator模式的基础。
 我们再来看看类AB,可以看到类AB和类A有依赖关系。我们知道,类的依赖不要依赖具体类,而要依赖于抽象类。所以我们需要给出类A和类AB的抽象类,即他们的接口:
 interface Ince
 {
  public void do();
 }
 那么我们的类A需要改写为:
 class A implements Ince
 {
  public void do()
  {
  ……
 }
 }
 类AB需要改写为:
 class AB implements Ince
 {
  private Ince ince;
  public AB()
  {
   ince = new A();
 }
  public void do
  {
   ince.do();
//do what B will do
 ……
 }
 }
 这样,依赖就满足了面向对象基本原则的依赖颠倒原则。但这样还不够,我们可以看到类AB既依赖接口类Ince,又依赖具体的实现类A。我们能不能把这个依赖也去掉呢?
 我们说,当然可以。
 Class AB implements Ince
 {
  private Ince ince;
  public AB(Ince ince)
  {
   this.ince = ince;
 }
 public void do()
 {
  ince.do();
 //do what B will do
 ……
 }
}
这样,具体的类由客户端注入,使得类AB不但可以接受类A的对象,同样也可以接受所有实现了Ince接口的类。这就是Decorator模式的一个典型做法。
 Decorator模式是一个结构性模式。他主要有以下的优点:
通过类之间的组合,来解决由于实际情况的复杂所引起的类的个数过于庞大的问题。Decorator模式的办法是定义一些类,然后由这些类来组合成各种各样的对象。
注意到我上面说的是Decorator模式通过一定一些类,然后由这些类来自由组合成其他的对象。注意,我说的是组合成其他的对象,而不是类。这又是Decorator模式跟类的继承不同之处,也就是它的第二个优点:Decorator模式组合得到的其他对象是动态的、或者说运行时的,这使得Decorator模式有了我们梦寐以求的灵活性和可扩展性。
 下面,我们通过对上面的咖啡的例子的解决来详细看看Decorator模式的解决问题的思路:
四、问题的解决
 我们的咖啡售卖系统要做两个事:第一,对售出的商品进行描述;第二,对售出的商品给出应收款项。
 首先,我们做一个接口:
 package decorator;
 
 
 public interface Drinking {
  public void describe();
  public double pay();
 
 }
 我们可以看到,这个Drinking的接口里声明了两个方法,分别用来对Drinking进行描述和计算应收款项。
 我们来看咖啡类的实现:
 package decorator;
 
 
 public class Coffee implements Drinking {
  public void describe()
  {
   System.out.print("Coffee!");
  }
  public double pay()
  {
   return 50;
  }
 
 }
 很明显,Coffee类是我们的主体类。
 然后我们来看加冰咖啡类:
 package decorator;
 
 
 public class IceCoffee implements Drinking {
  private Drinking drinking;
  public IceCoffee(Drinking drinking)
  {
   this.drinking = drinking;
  }
  public void describe()
  {
   System.out.print("ice adding,");
   this.drinking.describe();
  }
  public double pay()
  {
   return this.drinking.pay()+8.5;
  }
 
 }
 我们可以看到,类IceCoffee就是一个Decorator类。对于这个类,我们首先要往它的构造器注入一个实现了Drinking接口的类的对象;然后,它的decribe()方法的实现是先实现自己的行为,然后又执行组合类的行为;方法pay()也是这样。
 我们来看其他的类,都跟IceCoffee类的实现一样:
 package decorator;
 
 
 public class SugarCoffee implements Drinking {
  
  private Drinking drinking;
  public SugarCoffee(Drinking drinking)
  {
   this.drinking = drinking;
  }
  public void describe()
  {
   System.out.print("sugar adding,");
   this.drinking.describe();
  }
  public double pay()
  {
   return this.drinking.pay()+11.5;
  }
 
 }
 
 
 package decorator;
 
 public class CreamCoffee implements Drinking {
  
  private Drinking drinking;
  public CreamCoffee(Drinking drinking)
  {
   this.drinking = drinking;
  }
  public void describe()
  {
   System.out.print("cream adding,");
   this.drinking.describe();
  }
  public double pay()
  {
   return this.drinking.pay()+20.5;
  }
 
 }
 
 
 package decorator;
 
 public class ChocolateCoffee implements Drinking {
  
  private Drinking drinking;
  public ChocolateCoffee(Drinking drinking)
  {
   this.drinking = drinking;
  }
  public void describe()
  {
   System.out.print("chocolate adding,");
   this.drinking.describe();
  }
  public double pay()
  {
   return this.drinking.pay()+33.5;
  }
 
 }
 
 到此为止,我们实现了用Decrator模式来解决咖啡贩卖的问题,下面是我们的测试类:
 package decorator;
 
 public class Test {
 
  public static void main(String[] args) {
   Drinking coffee = new Coffee();
   //coffee added sugar
   Drinking sold1 = new SugarCoffee(coffee);
   sold1.describe();
   System.out.println();
   System.out.println(sold1.pay());
   Drinking sold2 = new ChocolateCoffee(new SugarCoffee(new Coffee()));
   sold2.describe();
   System.out.println();
   System.out.println(sold2.pay());
   
   Drinking sold3 = new CreamCoffee(new Coffee());
   sold3.describe();
   System.out.println();
   System.out.println(sold3.pay());
   
   Drinking sold4 = new CreamCoffee(new IceCoffee(new SugarCoffee(new ChocolateCoffee(new Coffee()))));
   sold4.describe();
   System.out.println();
   System.out.println(sold4.pay());
   
  }
 }
 
 测试结果如下:
 sugar adding,Coffee!
 61.5
 chocolate adding,sugar adding,Coffee!
 95.0
 cream adding,Coffee!
 70.5
 cream adding,ice adding,sugar adding,chocolate adding,Coffee!
 124.0
 
 以上就是咖啡馆问题的一个完整的解决过程,如果过了一段时间,咖啡馆又增加了一个新的咖啡品种:蒸汽加压咖啡;没关系,只要增加这个新类,就可以在客户端自由的使用这个类了,对已经存在的类不会做任何的改动:
 package decorator;
 
 public class Espresso implements Drinking {
  private Drinking drinking;
  public Espresso(Drinking drinking)
  {
   this.drinking = drinking;
  }
  public void describe()
  {
   System.out.print("espresso adding,");
   this.drinking.describe();
  }
  public double pay()
  {
   return this.drinking.pay()+18.5;
  }
 
 }
 
 现在,我们已经Decorator模式的开发过程以及它的优势全部展现给大家。最后,我们将以一个实际的例子来结束,并且将扩展该模式,使之在扩展功能的时候,连客户端都不需要做任何的修改。
五、实际的应用
 
 如上图,是一个Web页面的一部分。最上面是五个复选框,其中第一个是必选的,其余四个是可选的。如果选中其中的一个或几个选择框,那么他们对应的编辑栏就会在网页中出现;如果没有选中,则不在网页中出现。
 每一个编辑栏都有一些域,我们现在简单为文本域。我们可以在struts的配置文件里定义一个动态Form,将这些所有的域定义出来。然后我们在后台也会有一个POJO的类,用来在Action和Business层和DAO层传递数据。
 我们来看POJO类
 public class product {
  //must be
  private String mustA;
  private String mustB;
  //append one
  private String oneA;
  private String oneB;
  //append two
  private String twoA;
  private String twoB;
  private String twoC;
  //append three
  private String threeA;
  private String threeB;
  //append four
  private String fourA;
  private String fourB;
  private String fourC;
  private String fourD;
 
  public String getFourA() {
   return fourA;
  }
 
  public void setFourA(String fourA) {
   this.fourA = fourA;
  }
  
  public String getFourB() {
   return fourB;
  }
  
  public void setFourB(String fourB) {
   this.fourB = fourB;
  }
 
  public String getFourC() {
   return fourC;
  }
  public void setFourC(String fourC) {
   this.fourC = fourC;
  }
 
  public String getFourD() {
   return fourD;
  }
 
  public void setFourD(String fourD) {
   this.fourD = fourD;
  }
  
  public String getMustA() {
   return mustA;
  }
 
  public void setMustA(String mustA) {
   this.mustA = mustA;
  }
 
  public String getMustB() {
   return mustB;
  }
  
  public void setMustB(String mustB) {
   this.mustB = mustB;
  }
 
  public String getOneA() {
   return oneA;
  }
 
  public void setOneA(String oneA) {
   this.oneA = oneA;
  }
 
  public String getOneB() {
   return oneB;
  }
  
  public void setOneB(String oneB) {
   this.oneB = oneB;
  }
 
  public String getThreeA() {
   return threeA;
  }
 
  public void setThreeA(String threeA) {
   this.threeA = threeA;
  }
 
  public String getThreeB() {
   return threeB;
  }
 
  public void setThreeB(String threeB) {
   this.threeB = threeB;
  }
 
  public String getTwoA() {
   return twoA;
  }
  
  public void setTwoA(String twoA) {
   this.twoA = twoA;
  }
 
  public String getTwoB() {
   return twoB;
  }
 
  public void setTwoB(String twoB) {
   this.twoB = twoB;
  }
 
  public String getTwoC() {
   return twoC;
  }
  
  public void setTwoC(String twoC) {
   this.twoC = twoC;
  }
 }
 在这个类里,每个栏的域都有标出来。接下来我们要在Action里将页面取到的域的值赋给Product类对象,或者将Product类对象的值赋给页面,如下:
 product.setMustA((String)form.get(“mustA”));
 对于这样的取值赋值,我们知道,如果域mustA在页面上不存在的话,系统就会出错。而我们的这些值又恰恰是不能确定它们是否存在于页面上的。需要看复选框select的返回值。那么这样的赋值取值我们该怎么办呢?
 解决的办法就是使用Decorator模式。
 我们首先做一个Value接口:
 public interface Value {
  public void getFromWeb(Product product,DynaActionForm form);
  public void setToWeb(Product product,DynaActionForm form);
 
 }
 然后必输栏为我们的主体类:
 import org.apache.struts.action.DynaActionForm;
 
 public class MustColumn implements Value {
  public void getFromWeb(Product product,DynaActionForm form)
  {
   product.setMustA((String)form.get("mustA"));
   product.setMustB((String)form.get("mustB"));
  }
  public void setToWeb(Product product,DynaActionForm form)
  {
   form.set("mustA",product.getMustA());
   form.set("mustB",product.getMustB());
  }
 }
 
 来看看我们四个可选的栏位:
 import org.apache.struts.action.DynaActionForm;
 
 public class AppendOne implements Value {
  private Value value;
  public AppendOne(Value value)
  {
   this.value = value;
  }
  public void getFromWeb(Product product,DynaActionForm form)
  {
   this.value.getFromWeb(product,form);
   product.setOneA((String)form.get("oneA"));
   product.setOneB((String)form.get("oneB"));
  }
  public void setToWeb(Product product,DynaActionForm form)
  {
   this.value.setToWeb(product,form);
   form.set("oneA",product.getOneA());
   form.set("oneB",product.getOneB());
  }
 }
 
 import org.apache.struts.action.DynaActionForm;
 
 public class AppendTwo implements Value {
  private Value value;
  public AppendTwo(Value value)
  {
   this.value = value;
  }
  public void getFromWeb(Product product,DynaActionForm form)
  {
   this.value.getFromWeb(product,form);
   product.setTwoA((String)form.get("twoA"));
   product.setTwoB((String)form.get("twoB"));
   product.setTwoC((String)form.get("twoC"));
  }
  public void setToWeb(Product product,DynaActionForm form)
  {
   this.value.setToWeb(product,form);
   form.set("twoA",product.getTwoA());
   form.set("twoB",product.getTwoB());
   form.set("twoC",product.getTwoC());
  }
  
 }
 
 import org.apache.struts.action.DynaActionForm;
 
 public class AppendThree implements Value {
  private Value value;
  public AppendThree(Value value)
  {
   this.value = value;
  }
  public void getFromWeb(Product product,DynaActionForm form)
  {
   this.value.getFromWeb(product,form);
   product.setThreeA((String)form.get("threeA"));
   product.setThreeB((String)form.get("threeB"));
  }
  public void setToWeb(Product product,DynaActionForm form)
  {
   this.value.setToWeb(product,form);
   form.set("threeA",product.getThreeA());
   form.set("threeB",product.getThreeB());
  }
 
 }
 
 import org.apache.struts.action.DynaActionForm;
 
 public class AppendFour implements Value {
  private Value value;
  public AppendFour(Value value)
  {
   this.value = value;
  }
  public void getFromWeb(Product product,DynaActionForm form)
  {
   this.value.getFromWeb(product,form);
   product.setFourA((String)form.get("fourA"));
   product.setFourB((String)form.get("fourB"));
   product.setFourC((String)form.get("fourC"));
   product.setFourD((String)form.get("fourD"));
  }
  public void setToWeb(Product product,DynaActionForm form)
  {
   this.value.setToWeb(product,form);
   form.set("fourA",product.getFourA());
   form.set("fourB",product.getFourB());
   form.set("fourC",product.getFourC());
   form.set("fourD",product.getFourD());
  }
 
 }
 
 现在来看它们的客户端:
 String[] selects = request.getParameterValues("select");
 Value value = new MustColumn();
 For(int I=0;I<selects.length;I++)
 {
   if(select[i].equals(“1”))
   {
    value = new AppendOne(value);
 }
 else if(select[i].equals(“2”))
 {
  value = new AppendTwo(value);
 }
 else if(select[i].equals(“3”))
 {
  value = new AppendThree(value);
 }
 else if(select[i].equals(“4”))
 {
  value = new AppendFour(value);
 }
 }
 value.getFormWeb(product,form);
 
 我们可以看到,使用了Decorator模式后,这种回来赋值的确容易了很多。
 但我们同时可以看到还可以再简化,那段if…else if…应该是产生对象的过程,可以放在Factory类中去,这就是工厂模式:
 public class Factory
 {
   public static Value getInstance(String lable,Value value)
   {
    If(label.equals(“0”))
    {
     value = new MustColumn();
 }
    else if(lable.equals(“1”))
    {
     value = new AppendOne(value);
 }
 else if(lable.equals(“2”))
 {
   value = new AppendTwo(value);
 }
 else if(lable.equals(“3”))
 {
   value = new AppendThree(value);
 }
 else if(lable.equals(“4”))
 {
   value = new AppendFour(value);
 }
 else value = null;
 return value;
 }
 }
 有了这个工厂,则客户端可以改为:
 String[] selects = request.getParameterValues("select");
 Value value = Factory.getInstance(“0”,value);
 For(int I=0;I<selects.length;I++)
 {
   value = Factory.getInstance(selects[i],value);
 }
 value.getFormWeb(product,form);
 这样,客户端对装饰者类的依赖就没有了。
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页