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

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();
	}

	@Override
	public float cost() {
		return 13.8f;
	}

	@Override
	public String description() {
		return "牛奶";
	}

}

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

2.4人总是挑剔的,人的需求变得越来越多,人们开始不满足于仅仅只是牛奶了,他们喜欢搭配,比如有的人,就
喜欢双份牛奶+咖啡这样的组合。OK~,没问题啊,让我们再写多一个类吧~
public class DoubleMilkCoffee implements Beverage {

	@Override
	public float cost() {
		return 13.8f * 2 + 20.6f;
	}

	@Override
	public 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;
	}

	@Override
	public float cost() {
		return 13.8f + (beverage == null ? 0 : beverage.cost());
	}

	@Override
	public 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;

	@Override
	public float cost() {
		return BeveragePrices.COFFEE + (milk == null ? 0 : milk.cost())
				+ (orangeJuice == null ? 0 : orangeJuice.cost());
	}

	@Override
	public 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();
	}

	@Override
	public float cost() {
		float cost = selfCost;

		for (int i = 0; i < otherBeverages.size(); i++) {
			cost += otherBeverages.get(i).cost();
		}

		return cost;
	}

	@Override
	public 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 {

	@Override
	public float initSelfCost() {
		return BeveragePrices.MILK;
	}

	@Override
	public 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(水平有限,欢迎指正和讨论)



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值