合工大很牛很牛牛

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  14 Posts :: 1 Stories :: 37 Comments :: 0 Trackbacks

 

一个旅馆提供各种饮料(Beverage), 比如有HouseBlendDarkRoast等,每个饮料还有很多配料比如MilkSoy等。如何写一个程序能够方便的输出每种饮料的价格呢?(包括饮料+配料)

 

最笨的方法如下:也就是每种饮料,每种饮料加配料的组合,都写一个类。

package javaapplication33;

 

public class Main {

    public static void main(String[] args) {

        Beverage b=new HouseBlendWithMilk();

        System.out.println(b.getDescription());

        System.out.println(b.cost());

    }

}

 

abstract class Beverage {

    public abstract String getDescription();

    public abstract int cost();

}

 

class HouseBlend extends Beverage {

    @Override

    public String getDescription() {

        return "A cup of HouseBlend";

    }

    @Override

    public int cost() {

        return 10;

    }

}

 

class DarkRoast extends Beverage {

    @Override

    public String getDescription() {

        return "A cup of DarkRoast";

    }

    @Override

    public int cost() {

        return 8;

    }

}

 

class HouseBlendWithMilk extends Beverage{

    @Override

    public String getDescription() {

        return "A cup of HouseBlend with a soap of milk.";

    }

    @Override

    public int cost() {

       return 12;

    }

   

}

 

问题来了,如果饮料及其配料的排列组合有20种,30种,这很可能。那么是不是就要继承Beverage写上2030个类呢?这倒不是关键,比如万一Milk的价格变动了,那么所有有关Milk的类都要把cost()方法重写。。。。这才恐怖呢。原因是我们没有把变化的部分封装起来。

 

OK,我们开始封装。为方便起见,我们仍把主要的饮料HouseBlend, DarkRoast等写成父类Beverage的子类。我们把造成变化的配料MilkSoy等把它们封装起来,封装到哪?封装到父类里面去。看看下面这样行不行:

package javaapplication33;

 

public class Main {

    public static void main(String[] args) {

        Beverage b = new HouseBlend();

        b.setMilk(true); //增加一个配料

        System.out.println(b.cost());

        b.setSoy(true);

        System.out.println(b.getDescription());

        System.out.println(b.cost());

        b.setMilk(false);

        System.out.println(b.cost());

        b.setSoy(false);

        System.out.println(b.cost());

        System.out.println(b.getDescription());

    }

}

 

abstract class Beverage {

    int MILKPRICE = 2;

    int SOYPRICE = 3;

    public boolean milk;

    public boolean soy;

    int cost;

    String description;

    public void setMilk(boolean b) {

        milk = b;

    }

    public void setSoy(boolean b) {

        soy = b;

    }

    public int cost() {

        cost = 0;

        if (milk) {

            cost += MILKPRICE;

        }

        if (soy) {

            cost += SOYPRICE;

        }

        return cost;

    }

    public String getDescription() {

        description = "";

        if (milk) {

            description += " with a soap of milk";

        }

        if (soy) {

            description += " with a bottle of soy";

        }

        return description;

    }

}

 

class HouseBlend extends Beverage {

    @Override

    public String getDescription() {

        return "a cup of HouseBlend" + super.getDescription();

    }

    @Override

    public int cost() {

        return super.cost() + 10;

    }

}

 

class DarkRoast extends Beverage {

    @Override

    public String getDescription() {

        return "a cup of DarkRoast" + super.getDescription();

    }

    @Override

    public int cost() {

        return super.cost() + 8;

    }

}

 

这样,每当我需要一个饮料加几个配料时,仅需要new这个饮料,再set配料即可。貌似这个程序没问题了。

 

貌似!

(1)      当配料的价格改动时,Beverage这个类需要改动。设计模式的原则是在提供扩展的同时,尽可能不改动原有的程序的。

(2)       如果新增,或者减少某几种配料,同理,Beverage仍需改动。    

(3)       如果我们要双份的Milk呢?

崩溃!

 

以上,第一种思路是把所有的排列组合都写成子类,当一个配料变动,会导致所有相关的组合都要变动。

第二种思路是把饮料写成子类,配料的设置封装到父类中去,当配料变动时,会导致原有的父类变动,并且无法同时提供两份同样的配料。

 

其实当第一种思路被否定掉的时候就有一种冲动,就是把饮料和配料都写成子类,继承抽象的父类Beverage。这个其实很容易做到。只是主程序在调用时,比如我要一个HouseBlend配上Milk的时候,我该怎么生成呢?new一个HouseBlend,再new一个Milk,然后呢?怎么让它打印出“A Houseblend with a cup of milk”这样,并且算价格的时候直接两者的cost就能叠加呢?

 

我们想到了在读写文件操作时的样子: new XXX(new XXX(new XXXXX())); 如果饮料加上配料可以这样生成,岂不是全都解决了?

 

package javaapplication32;

 

public class Main {

    public static void main(String[] args) {

        Baverage b = new Milk(new HouseBland(new Soy()));

        System.out.println(b.getDescription());

        System.out.println(b.getCost());

    }

}

 

abstract class Baverage {

    abstract String getDescription();

    abstract int getCost();

}

 

class HouseBland extends Baverage {

    Baverage baverage;

    HouseBland() {

    }

    HouseBland(Baverage baverage) {

        this.baverage = baverage;

    }

    @Override

    String getDescription() {

        if (baverage != null) {

            return "A cup of HouseBland" + baverage.getDescription();

        }

        else {

            return "A cup of HouseBland";

        }

    }

    @Override

    int getCost() {

        if (baverage != null) {

            return 10 + baverage.getCost();

        }

        else {

            return 10;

        }

    }

}

 

class Milk extends Baverage {

    Baverage baverage;

    Milk() {

    }

    Milk(Baverage baverage) {

        this.baverage = baverage;

    }

    @Override

    String getDescription() {

        if (baverage != null) {

            return baverage.getDescription() + " And a soap Milk";

        }

        else {

            return " And a soap Milk";

        }

    }

    @Override

    int getCost() {

        if (baverage != null) {

            return baverage.getCost() + 5;

        }

        else {

            return 5;

        }

    }

}

 

class Soy extends Baverage {

    Baverage baverage;

    Soy() {

    }

    Soy(Baverage baverage) {

        this.baverage = baverage;

    }

    @Override

    String getDescription() {

        if (baverage != null) {

            return baverage.getDescription() + " And a bottle of Soy";

        }

        else {

            return " And a bottle of Soy";

        }

    }

    @Override

    int getCost() {

        if (baverage != null) {

            return baverage.getCost() + 7;

        }

        else {

            return 7;

        }

    }

}

 

问题解决了?可不可以更完美呢?我们发现每一个子类里面都用了大量的判断语句 

if (baverage != null) {…..} else {…..}

几乎都是一样的,为什么每个都要写呢?原因是我们没有人为的规定Baverage b = new Milk(new HouseBland(new Soy()));这个语句的顺序。是“配料(new 饮料()) 还是“饮料(new 配料)”?如果我们规定一下顺序的话,就有很多前面的if判断语句不需要反复写。

 

这个改进看起来好像不重要,但是,在实际工作中,像HouseBlend这样的类就像是原先写好的类,或者几乎长时间不会改变的类;而想配料Milk这样的类,就如同经常改动或者新增进去的类。也就是说前者不怎么改动,而后者会经常变。那么我们就要尽量保证前者的稳定和简单(越简单越不易出错)。

 

下面的程序,我们删除了HouseBlend中的繁杂的判断语句,在主函数生成饮料加配料时,我们要保证“new 配料(new 配料(….(new 饮料())))的模式(先配料后饮料)。(因为一个饮料可以配多个配料而一个配料不可以配多个饮料)

源代码如下:

package javaapplication32;

 

public class Main {

    public static void main(String[] args) {

        Baverage b = new Milk(new Soy(new HouseBlend()));

        System.out.println(b.getDescription());

        System.out.println(b.getCost());

    }

}

 

abstract class Baverage {

    abstract String getDescription();

    abstract int getCost();

}

 

class HouseBlend extends Baverage {

    @Override

    String getDescription() {

 

        return "A cup of HouseBland";

    }

    @Override

    int getCost() {

        return 10;

    }

}

 

class Milk extends Baverage {

    Baverage baverage;

    Milk() {

    }

    Milk(Baverage baverage) {

        this.baverage = baverage;

    }

    @Override

    String getDescription() {

        if (baverage != null) {

            return baverage.getDescription() + " And a soap Milk";

        }

        else {

            return " And a soap Milk";

        }

    }

    @Override

    int getCost() {

        if (baverage != null) {

            return baverage.getCost() + 5;

        }

        else {

            return 5;

        }

    }

}

 

class Soy extends Baverage {

    Baverage baverage;

    Soy() {

    }

    Soy(Baverage baverage) {

        this.baverage = baverage;

    }

    @Override

    String getDescription() {

        if (baverage != null) {

            return baverage.getDescription() + " And a bottle of Soy";

        }

        else {

            return " And a bottle of Soy";

        }

    }

    @Override

    int getCost() {

        if (baverage != null) {

            return baverage.getCost() + 7;

        }

        else {

            return 7;

        }

    }

}

 

其实我们还没揭示Decorator pattern的核心含义:配料跟主料其实不是一回事,但是为了实现主料的新功能,又要保持主料的稳定,我们就用配料继承主料,或者继承主料的父函数,目的就是获得相通的类型(type match)。

这样,我们可以在其他程序调用时,随时给主料添加配料的新功能。这就是composition组合的魅力!

 

最后,我们对上面的程序做一下最终的改进,原因是由于在类的层次设计的时候,我们没有区分饮料和配料之间的关系,因为它们都平行的继承了同一个抽象类。在Decorator pattern里面,往往会出现这样的情况:就是饮料的种类很稳定,而配料的种类却很繁杂。为了让程序看上去更清晰(一眼就能看出谁是主,谁是配),我们用特定的一个抽象类继承原先的父类,再让所有的配料继承该抽象类。

 

package javaapplication32;

 

public class Main {

    public static void main(String[] args) {

        Baverage b = new Milk(new Soy(new HouseBlend()));

        System.out.println(b.getDescription());

        System.out.println(b.getCost());

    }

}

 

abstract class Baverage {

    abstract String getDescription();

    abstract int getCost();

}

 

class HouseBlend extends Baverage {

    @Override

    String getDescription() {

 

        return "A cup of HouseBland";

    }

    @Override

    int getCost() {

        return 10;

    }

}

 

abstract class Decorator extends Baverage {

    abstract String getDescription();

    abstract int getCost();

}

 

class Milk extends Decorator {

    Baverage baverage;

    Milk() {

    }

    Milk(Baverage baverage) {

        this.baverage = baverage;

    }

    @Override

    String getDescription() {

        if (baverage != null) {

            return baverage.getDescription() + " And a soap Milk";

        }

        else {

            return " And a soap Milk";

        }

    }

    @Override

    int getCost() {

        if (baverage != null) {

            return baverage.getCost() + 5;

        }

        else {

            return 5;

        }

    }

}

 

class Soy extends Decorator {

    Baverage baverage;

    Soy() {

    }

    Soy(Baverage baverage) {

        this.baverage = baverage;

    }

    @Override

    String getDescription() {

        if (baverage != null) {

            return baverage.getDescription() + " And a bottle of Soy";

        }

        else {

            return " And a bottle of Soy";

        }

    }

    @Override

    int getCost() {

        if (baverage != null) {

            return baverage.getCost() + 7;

        }

        else {

            return 7;

        }

    }

}

 

其实java API里面有很多使用Decorator Pattern的例子,比如读写文件:

posted on 2008-07-04 10:59 化的了 阅读(934) 评论(1)  编辑  收藏 所属分类: 设计模式

Feedback

# re: 配件模式 Decorator Pattern 2008-07-04 13:13
写得很好。
受益了。  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: