用户操作
[即时聊天] [发私信] [加为好友]
冯国平ID:hivon
213421次访问,排名329好友18人,关注者79
hivon的文章
原创 93 篇
翻译 28 篇
转载 3 篇
评论 304 篇
hivon的公告
有人说,交换一个苹果,我们每人仍然只有一个苹果;交换一个思想,我们每人却有两个思想!
最近评论
jjservice:HEHE
jjservice:来了
jjservice:来了
jjservice:来了
nbkangta:不错,很受用,谢谢
文章分类
收藏
    相册
    交换链接
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 组合的魔力——模式系列谈之Decorator模式收藏

    新一篇: 行为的封装——模式系列谈之Command模式 | 旧一篇: AJAX实例入门

                                                                            组合的魔力
                                                                 ——模式系列谈之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);
     这样,客户端对装饰者类的依赖就没有了。

    发表于 @ 2005年12月21日 10:21:00|评论(loading...)|编辑

    新一篇: 行为的封装——模式系列谈之Command模式 | 旧一篇: AJAX实例入门

    评论:没有评论。

    发表评论  


    当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
    Csdn Blog version 3.1a
    Copyright © hivon