配件模式 Decorator Pattern

来源:互联网 发布:python socket连接池 编辑:程序博客网 时间:2024/04/24 15:03
一个旅馆提供各种饮料(Beverage), 比如有HouseBlend,DarkRoast等,每个饮料还有很多配料比如Milk,Soy等。如何写一个程序能够方便的输出每种饮料的价格呢?(包括饮料+配料)
 
最笨的方法如下:也就是每种饮料,每种饮料加配料的组合,都写一个类。
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写上20,30个类呢?这倒不是关键,比如万一Milk的价格变动了,那么所有有关Milk的类都要把cost()方法重写。。。。这才恐怖呢。原因是我们没有把变化的部分封装起来。
 
OK,我们开始封装。为方便起见,我们仍把主要的饮料HouseBlend, DarkRoast等写成父类Beverage的子类。我们把造成变化的配料Milk,Soy等把它们封装起来,封装到哪?封装到父类里面去。看看下面这样行不行:
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的例子,比如读写文件:
原创粉丝点击