设计模式——4.装饰模式

来源:互联网 发布:汽车行业的大数据应用 编辑:程序博客网 时间:2024/05/01 23:34
1.依旧是我的风格,从后到前,先看答案:

装饰者模式——装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。(复制于Head First设计模式)

2.发现问题,解决问题:
比如你开一间饮料店,贩卖牛奶,咖啡,橙汁等等(当然,现在的饮料这么多种,为什么我列举这三种呢~~因为我英语不好= =,会拼写的单词不多。所以类名的话,就只知道这几个了╮(╯▽╰)╭)
那么,先让我们看看,我们的初始装备是怎么样的吧:

2.1一个代表饮料的接口(就两个方法,这里就不解释了):

public interface Beverage {float cost();String description();}

2.2代表牛奶的具体类(为什么后面有个old呢,那是因为这个不是最终版的代码):

public class Milk_old implements Beverage {public Milk_old() {super();}@Overridepublic float cost() {return 13.8f;}@Overridepublic String description() {return "牛奶";}}

2.3代表咖啡和橙汁的具体类,跟上面的代码,大同小异,都是实现了Beverage接口,然后写上了自己特定的描
述和价钱,这里为了篇幅,就不列举出来了,有兴趣的各位,可以在末尾处,下载源码查看。

2.4人总是挑剔的,人的需求变得越来越多,人们开始不满足于仅仅只是牛奶了,他们喜欢搭配,比如有的人,就
喜欢双份牛奶+咖啡这样的组合。OK~,没问题啊,让我们再写多一个类吧~
public class DoubleMilkCoffee implements Beverage {@Overridepublic float cost() {return 13.8f * 2 + 20.6f;}@Overridepublic String description() {return "双份牛奶的咖啡";}}

问题看起来是初步解决了,我们的贩卖系统中,又多一份新品,又开始疯狂的挣钱了~
当然,这个类的代码里面还是有不优雅的地方,比如那个cost方法里的价钱,计算部分。它其实就是两份牛奶的价钱(13.8)+加上一份咖啡的价钱(20.6),但是现在这样是很不友好的,因为别人维护的时候,不知道你这个算式是怎么来的。
如果,我们想让这条算式,更人性化一点的话,我们应该建立一个常量表:
public class BeveragePrices {public final static float MILK = 13.8f;public final static float COFFEE = 20.6f;public final static float ORANGE_JUICE = 7.8f;}


那么cost方法里面,就应该变成如下这样了:
return BeveragePrices.MILK * 2 + BeveragePrices.COFFEE;

当然,写法有很多,这里就不继续叙说了,因为这并不是这篇文章的重点。

2.5类爆炸问题
现在的系统,看起来运行得很完美,没有一点问题是吧。但是,正如前面说的那样,人总是挑剔的,现在有人想要双份牛奶的咖啡,但是很快就有人想要三份牛奶混咖啡的,也有的人仅仅是想要一份牛奶混咖啡的,更恐怖的~还有牛奶混橙汁再混咖啡的。
那我们应该怎么解决呢?莫非,我们要像上面的DoubleMilkCoffee一样,再写多三个类么?再来一个OneMilkCoffee?TripleMilkCoffee?OrangeJuiceAddMilkAddCoffee?
别闹了~万一以后再有人,想要一个双份牛奶混咖啡混橙汁的呢?天哪,要死人了。那我们得写多少个类啊!!!而且,明明这些组合,都是基于当前系统中已经有东西,难道我们就没有更聪明一点的办法吗?
2.6装饰模式
当然,肯定是有的,这里就说到了我们这次的主题:装饰模式。
首先,让我们来看看,变身之后的Milk这个类是怎么样的吧:
public class Milk implements Beverage {private Beverage beverage;public Milk() {super();}public Milk(Beverage beverage) {super();this.beverage = beverage;}@Overridepublic float cost() {return 13.8f + (beverage == null ? 0 : beverage.cost());}@Overridepublic String description() {return "牛奶"+ (beverage == null ? "" : "+" + beverage.description());}public Beverage getBeverage() {return beverage;}public void setBeverage(Beverage beverage) {this.beverage = beverage;}}
跟原本的有什么不同呢?最大的不同,就是在于它持有了一个Beverage的引用,那多了一个这样的东西之后,到底有什么神奇的作用呢?让我们一起看看,下面这个测试例子吧,下面这个测试例子,展示了一个双份牛奶的咖啡

public static void main(String[] args) {Beverage beverag = new Coffee();beverag = new Milk(beverag);beverag = new Milk(beverag);System.out.println("总花费:" + beverag.cost());System.out.println("总描述:" + beverag.description());}
输出结果为:
总花费:48.2
总描述:牛奶+牛奶+咖啡

如果以后有人想要三份牛奶的咖啡呢?没问题啊~你懂的,哪怕是橙汁加牛奶都没问题了,你懂的。
很明显,通过这个装配模式,只要基类是存在的(牛奶,咖啡),以后不管再怎么组合装配,都没问题了。

3.另外一种解决方法,引出设计原则(开放-关闭)
好了,可能在还没有看到答案之前,各位都有自己的想法,有的人可能会想出了下面这个解决方案:
public class Coffee_old implements Beverage {private Milk milk;private OrangeJuice orangeJuice;@Overridepublic float cost() {return BeveragePrices.COFFEE + (milk == null ? 0 : milk.cost())+ (orangeJuice == null ? 0 : orangeJuice.cost());}@Overridepublic String description() {return "这里就省略不写了";}}


这样的话,看似解决了问题,这个咖啡不再是普通的咖啡,可以在使用的时候,利用的组合的威力,动态的为它添加牛奶和橙汁,方便的形成咖啡+牛奶,或者咖啡+橙汁这样的组合。但是,它还是无法解决上面提到的,万一有人要双份奶的咖啡,而仅仅只是一份奶的咖啡呢?所以,上面这种写法,还是不能很好的解决问题。

这里就体现了一个很重要的设计原则,就是:类应该对扩展开放,对修改关闭
简单来说,就是如果以后我要增加一个奶混茶这样的一个组合时,我不应该再修改牛奶这个类的代码,我只需要添加一个茶,这个新类就可以了。而且,还能拓展出奶混茶这样的新组合。

4.另外一种实现
在这种实现里面,Beverage接口跟上面的还是一样的,不同的在于,在接口到具体类的中间,我加了一个抽象类,如下:
import java.util.ArrayList;import java.util.List;public abstract class TemplateBeverage implements Beverage {private List<TemplateBeverage> otherBeverages = new ArrayList<TemplateBeverage>();private float selfCost;private String selfDescription;public TemplateBeverage() {super();selfCost = initSelfCost();selfDescription = initSelfDescription();}@Overridepublic float cost() {float cost = selfCost;for (int i = 0; i < otherBeverages.size(); i++) {cost += otherBeverages.get(i).cost();}return cost;}@Overridepublic String description() {StringBuilder builder = new StringBuilder(selfDescription);for (int i = 0; i < otherBeverages.size(); i++) {builder.append("+").append(otherBeverages.get(i).description());}return builder.toString();}public List<TemplateBeverage> getOtherBeverages() {return otherBeverages;}public void setOtherBeverages(List<TemplateBeverage> otherBeverages) {this.otherBeverages = otherBeverages;}public void addOtherBeverage(TemplateBeverage otherBeverage) {this.otherBeverages.add(otherBeverage);}/** * 返回这个饮料,在不附加任何其他东西,不与其他任何饮料搭配的情况下,购买自身的花费 *  * @return */public abstract float initSelfCost();/** * 返回这个饮料,在不附加任何其他东西,不与其他任何饮料搭配的情况下,自身的描述信息 *  * @return */public abstract String initSelfDescription();}

以及新的牛奶类(其余的雷同,这里不一一给出):

public class Milk extends TemplateBeverage {@Overridepublic float initSelfCost() {return BeveragePrices.MILK;}@Overridepublic String initSelfDescription() {return "牛奶";}}

测试代码:
public class Test {public static void main(String[] args) {TemplateBeverage coffee = new Coffee();coffee.addOtherBeverage(new Milk());coffee.addOtherBeverage(new Milk());System.out.println("总花费:" + coffee.cost());System.out.println("总描述:" + coffee.description());}}

输出结果:
总花费:48.2
总描述:咖啡+牛奶+牛奶

在这一种实现中,个人觉得添加新的饮料时,需要新增的代码更少,每一个具体类(如牛奶)只需要告诉我自身价钱以及自身描述信息就好了。而且,对于追溯最终售出的饮料,是与多少种其他饮料搭配时也变得更加的方便了。当然,坏处的话,就是每一个具体类从实现一个接口,变成了继承一个抽象类,在java中是不允许多继承的,这又是一个麻烦。不过,这里并不对这种实现继续展开讨论了。因为这次的重点,是讲装饰模式。



5.最后补充(图均来自Head First设计模式)



参考代码:http://pan.baidu.com/s/1cDRG1C(水平有限,欢迎指正和讨论)



0 0