前言:使用装饰者的目的(why)
装饰者采用运行时扩展,远比编译时期的继承威力大。它还能够在不修改底层代码的情况下,给你的或者别人的对象赋予新的职责。
先来杯咖啡
Boss的咖啡店与时俱进准备搞一套网上点咖啡系统,它们最初的代码设计是这样的:
咖啡要加调料的,例如:牛奶、豆浆、摩卡等,所以设计修改为:
类太多,而且每个类有自己的cost()方法,一旦牛奶价格上涨,所有与之有关的类就得修改,维护很麻烦。
我们可以利用实例变量与继承减少类,调料定义为布尔型实例变量,判断有无添加调料,代码如下:
public abstract class Beverage {
public String des="咖啡";
public boolean milk;
public boolean soy;
public boolean mocha;
public boolean whip;
private double cost;
private double milkCost=5.00;
private double mochaCost=6.00;
public String getDes() {
return des;
}
public double cost(){
if (isMocha()) {
cost+=mochaCost;
}else if (isMilk()){
cost+=milkCost;
}
return cost;
}
public boolean isMilk() {
return milk;
}
public void setMilk(boolean milk) {
this.milk = milk;
}
public boolean isSoy() {
return soy;
}
public void setSoy(boolean soy) {
this.soy = soy;
}
public boolean isMocha() {
return mocha;
}
public void setMocha(boolean mocha) {
this.mocha = mocha;
}
public boolean isWhip() {
return whip;
}
public void setWhip(boolean whip) {
this.whip = whip;
}
}
此时父类的cost()方法不再是抽象的,它需要通过if-else语句判断是否加了某种调料,从而加上调料费用。
而它的子类,还需要加上本身咖啡的费用,于是最终费用=调料+子类本身费用。只举其中一个子类饮料的列子,代码如下:
public class DarkRoast extends Beverage {
private double darkRoastCost = 1.99;
public DarkRoast() {
des = "超优深焙";
}
@Override
public double cost() {
return darkRoastCost+super.cost();
}
}
此时我点了杯"超优深焙"的咖啡,并加了牛奶作为调料,价格为1.99+5=6.99,运行代码如下:
public class DecorateTest {
public static void main(String[] args) {
DarkRoast darkRoast = new DarkRoast();
darkRoast.setMilk(true);
System.out.println(darkRoast.getDes());
System.out.println(darkRoast.cost());
}
}
运行结果如预料:
然而,当需求改变,比如调料价格改变、添加新调料、新饮料等,代码也得随之改变。我们需要遵循开放-关闭原则:类应该对扩展开放,对修改关闭。使用装饰者模式就是个好例子。
什么是装饰者模式
装饰者模式动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的方案。类图如下:
我们的咖啡点餐系统代码套用此类图:
抽象组件:饮料基类Beverage的代码不需要改变原始设计,如下:
public abstract class Beverage {
public String des="咖啡";
public String getDes() {
return des;
}
public abstract double cost();
}
为了让调料抽象装饰者CondimentDecorator能够取代饮料Beverage,所以CondimentDecorator扩展自Beverage,使用继承是为了有正确的类型,而不是继承它的行为。getDes()定义为抽象方法,由子类重新实现,抽象类代码如下:
public abstract class CondimentDecorator extends Beverage{
public abstract String getDes();
}
具体组件饮料的代码,使用构造器初始化饮料描述,des实例变量继承自基类Beverage,并实现cost()方法。代码如下:
public class DarkRoast extends Beverage {
public DarkRoast() {
des = "超优深焙";
}
@Override
public double cost() {
return 1.99;
}
}
我们点了摩卡Mocha(具体装饰者)口味的,具体装饰者代码如下:
public class Mocha extends CondimentDecorator {
//定义一个实例变量记录被装饰者
Beverage beverage;
//使用构造器来记录被装饰者
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDes() {
return beverage.getDes() + " Mocha";
}
/**
* 首先把调用委托给被装饰着计算费用,再加上Mocha的费用
* @return
*/
@Override
public double cost() {
return beverage.cost() + 0.2;
}
}
我们还加了豆浆(具体装饰者),代码如下:
public class Soy extends CondimentDecorator {
//定义一个实例变量记录被装饰者
Beverage beverage;
//使用构造器来记录被装饰者
public Soy(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDes() {
return beverage.getDes() + " Soy";
}
@Override
public double cost() {
return beverage.cost() + 0.15;
}
}
我们点餐完毕,运行程序代码:
public class StartTest {
public static void main(String[] args) {
Beverage darkRoast = new DarkRoast();
Soy soy = new Soy(darkRoast);
Mocha mocha = new Mocha(soy);
System.out.println(mocha.getDes());
System.out.println(mocha.cost());
}
}
运行结果如下:
装饰者模式典型案例:java I/O
和上面咖啡的设计相比,java.io并没有多大差异。
现在我们应用下装饰者把输入流内所有大写字符转换成小写。具体装饰者代码如下:
public class LowerCaseInputStream extends FilterInputStream {
/**
* Creates a <code>FilterInputStream</code>
* by assigning the argument <code>in</code>
* to the field <code>this.in</code> so as
* to remember it for later use.
*
* @param in the underlying input stream, or <code>null</code> if
* this instance is to be created without an underlying stream.
*/
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<len+result;i++) {
b[i] = (byte)Character.toLowerCase((char)b[i]);
}
return result;
}
}
运行代码如下:
public class InputTest {
public static void main(String[] args) throws IOException {
int c;
InputStream lowerCaseInputStream = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("D:/test.txt")));
System.out.println(lowerCaseInputStream.read());
while ((c=lowerCaseInputStream.read())>=0) {
System.out.print((char)c);
}
lowerCaseInputStream.close();
}
}
D盘的test.txt内容为:OWeN_JaMES,运行代码,结果如下: