设计模式学习之三装饰者模式(Decorator)——解决星巴兹扩张快的问题

星巴兹是以扩张速度最快而闻名的咖啡连锁店。如果你在街角看到它的店,在对面街上肯定还会看到另一家。因为扩张速度实在太快了,他们准备更新订单系统,以合乎他们的饮料供应要求。他们原先的类设计是这样的:

购买咖啡时,也可以要求在其中加入各种调料,例如:蒸奶(SteamedMilk)、豆浆(Soy)、摩卡(Mocha,也就是巧克力风味)或覆盖奶泡。星巴兹会根据所加入的调料收取不同的费用。所以订单系统必须考虑到这些调料部分。这是他们的第一个尝试……

好吧,这简直是“类爆炸”。星巴兹为自己制造了一个维护噩梦,如果牛奶的价钱上扬怎么办?新增一种调料风味时,怎么办?

好吧,有人认为上面的设计“笨透了”(这是事实),利用实例变量和继承,就可以追踪这些调料。那我们试试吧,设计类图如下:

但是如果用上面的设计,如果调料价钱改变会使我们更改现有的代码,一旦出现新的调料,我们需要加上新的方法,并且改变超类中的cost()方法。。。。。。上面违背了一个非常重要的原则之一:类应该对扩展开放,对修改关闭。

到了解决真正问题的时候了,在解决问题之前先让我们去认识一下装饰者模式,也就是解决这个问题需要用到的模式。解决问题的办法就是以饮料为主题,然后在运行时以调料来“装饰”饮料。装饰者模式动态地将责任附加到对象上。想要扩展功能,装饰者提供了有别于继承的另一种选择。类图如下:

好吧,现在要开始装饰我们的饮料了,让星巴兹饮料也能符合此框架:

从上面我们能看出:

1. 装饰者和被装饰对象有相同的超类型。

2. 可以用一个或多个装饰者包装一个对象。

3. 装饰者可以在所委托被装饰者的行为之前或之后,加上自己的行为,以达到特定的目的。

4. 对象可以在任何时候被装饰,所以可以在运行时动态的,不限量的用你喜欢的装饰者来装饰对象。

5. 装饰模式中使用继承的关键是想达到装饰者和被装饰对象的类型匹配,而不是获得其行为。

现在该是把设计变成真正代码的时候了!先从Beverage类下手,这不需要改变星巴兹原始的设计:

public abstract class Beverage { String description = "Unknown Beverage"; public String getDescription() { return description; } public abstract double cost(); }

Beverage类很简单,让我们也来实现Condiment(调料)抽象类,也就是装饰者类:

public abstract class CondimentDecorator extends Beverage { public abstract String getDescription(); }

现在已经有了基类,让我们开始实现一些饮料吧。别忘了我们需要为具体的饮料设置描述,而且还必须实现cost()方法:

public class Espresso extends Beverage { public Espresso() { description = "Espresso Coffee"; } @Override public double cost() { return 1.99; } }

public class HouseBlend extends Beverage { public HouseBlend() { description = "HouseBlend Coffee"; } @Override public double cost() { return 0.89; } }

public class DarkRoast extends Beverage { public DarkRoast() { description = "DarkRoast Coffee"; } @Override public double cost() { return 0.99; } }

public class Decaf extends Beverage { public Decaf() { description = "Decaf Coffee"; } @Override public double cost() { return 1.05; } }

如果你回头看类图,现在我们已经实现了抽象组件(Beverage),有了具体组件(HouseBlend。。。),也有了抽象装饰者(CondimentDecorator),现在让我们来实现具体装饰者:

public class Mocha extends CondimentDecorator { Beverage beverage; public Mocha(Beverage beverage) { this.beverage = beverage; } @Override public String getDescription() { return beverage.getDescription() + ", Mocha"; } @Override public double cost() { return 0.20 + beverage.cost(); } }

public class Soy extends CondimentDecorator { Beverage beverage; public Soy(Beverage beverage) { this.beverage = beverage; } @Override public String getDescription() { return beverage.getDescription() + ", Soy"; } @Override public double cost() { return 0.15 + beverage.cost(); } }

public class Whip extends CondimentDecorator { Beverage beverage; public Whip(Beverage beverage) { this.beverage = beverage; } @Override public String getDescription() { return beverage.getDescription() + ", Whip"; } @Override public double cost() { return 0.10 + beverage.cost(); } }

OK,现在我们可以坐下来来享受劳动成果了,点几种咖啡尝尝,下订单吧:

public class StarbuzzCoffee { public static void main(String[] args) { //订一杯Espresso,不需要调料 Beverage beverage = new Espresso(); System.out.println(beverage.getDescription() + " $" + beverage.cost()); Beverage beverage2 = new DarkRoast(); //用Mocha装饰它 beverage2 = new Mocha(beverage2); //用第二个Mocha装饰它 beverage2 = new Mocha(beverage2); //用Whip装饰它 beverage2 = new Whip(beverage2); System.out.println(beverage2.getDescription() + " $" + beverage2.cost()); //再来一杯调料为豆浆、摩卡、奶泡的HouseBlend咖啡 Beverage beverage3 = new HouseBlend(); beverage3 = new Soy(beverage3); beverage3 = new Mocha(beverage3); beverage3 = new Whip(beverage3); System.out.println(beverage3.getDescription() + " $" + beverage3.cost()); } }

真实世界也存在诸多的装饰者,学过Java IO的就知道IO就采用了装饰者模式。下面是装饰的IO类图:

既然你已经知道装饰者模式,也看了Java IO的类图了,那么我们是否可以试着编写自己的输入装饰者了,那么试着编写一个装饰者吧,把输入流的所有大写字符转换成小写:

import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; public class LowerCaseInputStream extends FilterInputStream { protected LowerCaseInputStream(InputStream in) { super(in); } /** * 针对字节 * @Override */ public int read() throws IOException { int c = super.read(); return (c==-1?c:Character.toLowerCase((char)c)); } /** * 针对字节数组 * @Override */ public int read(byte[] b, int off, int len) throws IOException { int result = super.read(b, off, len); for(int i=off;i<off+result;i++) { b[i] = (byte)Character.toLowerCase((char)b[i]); } return result; } }

测试代码:

import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; public class InputTest { public static void main(String[] args) { int c; try { InputStream in = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("test.txt"))); while((c = in.read())>=0) { System.out.print((char)c); } in.close(); } catch(IOException e) { e.printStackTrace(); } } }

OK,装饰者模式到此算告一段落了。。。。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值