设计模式学习之装饰者模式
定义
动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的方案。
案例
现在一个咖啡厅要升级订单系统,当购买咖啡时,可以加入相应的调料。例如:蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha)或奶泡。并自动计算咖啡+调料的价格。
要知道咖啡也有很多种,比如有以上种类的咖啡和配料以及价格。
那么我们可以考虑用装饰者模式了。
首先定义一个饮料的超类
public abstract class Beverage {
public String description = "Unknown Beverage";
//饮料的说明
public String getDescription() {
return description;
}
//饮料的价格
public abstract double cost();
}
另外还会有综合、深培、低咖啡因、浓缩等具体的咖啡类
/**
* 具体的咖啡
* 深培咖啡
*
*/
public class DarkRoast extends Beverage {
public DarkRoast() {
//给饮料说明赋值
description = "DarkRoast";
}
//计算饮料的价钱
public double cost() {
return 1.99;
}
}
/**
* 具体的咖啡
* 低咖啡因咖啡
* @author user1
*
*/
public class Decaf extends Beverage {
public Decaf() {
//给饮料说明赋值
description = "Decaf";
}
//计算饮料的价钱
public double cost() {
return 1.05;
}
}
Beverage很简单。让我们也来实现Condiment(调料)抽象类,也就是装饰者类吧:
/**
为何装饰者需要和被装饰者(亦即被包装的组件)有相同的“接口”,因为装饰者必须能取代被装饰者。
继承Beverage抽象类,是为了有正确的类型,而不是继承它的行为。行为来自装
饰者和基础组件,或与其他装饰者之间的组合关系。
*/
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
接下来实现具体的装饰者
/**
* 调料 具体的装饰者
* 摩卡
* @author user1
*
*/
public class Mocha extends CondimentDecorator {
Beverage beverage;
/*
* 要让Mocha能够引用一个Beverage,做
法如下:
(1)用一个实例变量记录饮料,也就
是被装饰者。
(2)想办法让被装饰者(饮料)被记
录到实例变量中。这里的做法是:把
饮料当作构造器的参数,再由构造器
将此饮料记录在实例变量中。
*/
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
/*
* 我们希望叙述不只是描述饮料(例
如“Mocha”),而是完整地连调料都
描述出来(例如“DarkRoast, Mocha”)。
所以首先利用委托的做法,得到一个
叙述,然后在其后加上附加的叙述(例
如“Mocha”)
* */
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
public double cost() {
return .20 + beverage.cost();
}
}
/**
* 调料 具体的装饰者
* 奶泡
* @author
*
*/
public class Whip extends CondimentDecorator {
Beverage beverage;
/*
* 要让Mocha能够引用一个Beverage,做
法如下:
(1)用一个实例变量记录饮料,也就
是被装饰者。
(2)想办法让被装饰者(饮料)被记
录到实例变量中。这里的做法是:把
饮料当作构造器的参数,再由构造器
将此饮料记录在实例变量中。
*/
public Whip(Beverage beverage) {
this.beverage = beverage;
}
/*
* 我们希望叙述不只是描述饮料(例
如“DarkRoast”),而是完整地连调料都
描述出来(例如“DarkRoast, Mocha”)。
所以首先利用委托的做法,得到一个
叙述,然后在其后加上附加的叙述(例
如“Mocha”)
* */
public String getDescription() {
return beverage.getDescription() + ", Whip";
}
public double cost() {
return .10 + beverage.cost();
}
}
接下来测试一下下订单,看能不能算出咖啡+调料的总价格
public class StarbuzzCoffee {
public static void main(String args[]) {
//定一杯深培咖啡,不加调料,显示描述与价格
Beverage beverage = new DarkRoast();
System.out.println(beverage.getDescription() + " $" + beverage.cost());
// 定一杯低咖啡因咖啡,加摩卡,加奶泡,显示描述与价格
Beverage beverage2 = new Decaf();
beverage2 = new Mocha(beverage2);
System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
//定一杯 定一杯低咖啡因咖啡,加摩卡,加奶泡,显示描述与价格
Beverage beverage3 = new Whip(new Mocha(new HouseBlend())); //这种写法是不是似曾相识,是的,当我们包装输入输出流时,通常是这样的。
System.out.println(beverage3.getDescription() + " $" + beverage3.cost());
}
}
真实世界的装饰者
Java Io包内许多类都是装饰者
BufferedInputStream及LineNumberInputStream都扩展自
FilterInputStream,而FilterInputStream是一个抽象的装饰类。
你会发现和我们的咖啡例子并没有多大的差异。